IOC(控制反转)是一种设计思想或者说是理论,下面通过一个简单的示例进行推导。
开发环境
新建项目
1、使用 idea 和 maven 创建项目,目录结构如下

2、在父 pom.xml 文件中,引入以下两个依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.yuhuofei</groupId>
<artifactId>spring-study</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>01-spring-ioc</module>
</modules>
<dependencies>
<!--引入spring框架依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
<type>pom</type>
</dependency>
<!--引入jdbc依赖包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
</dependencies>
</project>
3、在 dao 目录下新建以下接口及实现类
package com.yuhuofei.dao;
/**
* @Description
* @InterfaceName UserDao
* @Author yuhuofei
* @Date 2022/6/14 22:00
* @Version 1.0
*/
public interface UserDao {
public void selectUser();
}
package com.yuhuofei.dao;
/**
* @Description
* @ClassName UserDaoImpl
* @Author yuhuofei
* @Date 2022/6/14 22:01
* @Version 1.0
*/
public class UserDaoImpl implements UserDao {
@Override
public void selectUser() {
System.out.println("获取当前的用户信息");
}
}
4、在 service 目录下,新建以下接口及实现类
package com.yuhuofei.service;
/**
* @Description
* @InterfaceName UserService
* @Author yuhuofei
* @Date 2022/6/14 22:02
* @Version 1.0
*/
public interface UserService {
public void getUser();
}
package com.yuhuofei.service;
import com.yuhuofei.dao.UserDao;
import com.yuhuofei.dao.UserDaoImpl;
/**
* @Description
* @ClassName UserServiceImpl
* @Author yuhuofei
* @Date 2022/6/14 22:02
* @Version 1.0
*/
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.selectUser();
}
}
至此,一个简单的 java 小项目就搭建完了。
5、新建一个测试类
在 test/java 目录下,新建一个测试类 TestIoc.java ,内容如下:
import com.yuhuofei.service.UserService;
import com.yuhuofei.service.UserServiceImpl;
/**
* @Description
* @ClassName TestIoc
* @Author yuhuofei
* @Date 2022/6/16 23:46
* @Version 1.0
*/
public class TestIoc {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
运行里面的 main 方法,控制台输出下面的结果

以上是比较原始的 java 实现方式,当没有需求变更时,没有任何问题,但是,需求总是在变化的。
在前面的实现例子中可以发现,客户端调用的实际是 service 层的接口,与 dao 层并不直接接触。
这时候,如果有一个新的需求过来,要查询小明这个用户的信息,该怎么做?
按以往的开发方式,我们肯定是在 dao 层新增一个实现类(如 XiaoMingUserDaoImpl.java),实现小明这个用户的信息查询,然后更改 service 层的原有实现类,将 userDao 指向 小明这个实现类 new 出来的对象。(即:UserDao userDao = new XiaoMingUserDaoImpl(); )
最后再由客户端调用,得到结果。
上面的这种方式,会有这样的问题,需求每变更一次,都要同时更改 dao 层、service 层的代码,而且调用具体实现方法的控制权在程序或者说是程序员手上,不在调用方手里。这样的方式来应对频繁的需求变更,对程序员来说是非常痛苦非常累的。
例如,要依次查询小红、小李、小姚……等50个人的用户信息,那代码得改得多幸苦!
那有没有什么方法改进,减少工作量的?当然有!
在 java 的类中,对于类的成员变量,一般会有与之对应的 get 或者 set 方法来获取或者修改该成员变量的值。利用这一点,可以对上面的实现方式进行改进。
实现方法改进
对于 dao 层的内容,可以先准备好所有的接口实现类,例如 XiaoHongUserDaoImpl.java、XiaoLiUserDaoImpl.java、XiaoYaoUserDaoImpl.java……查询各自对应的结果
对于 service 层的实现类,利用 set 方法可以改成以下的实现方式
package com.yuhuofei.service;
import com.yuhuofei.dao.UserDao;
/**
* @Description
* @ClassName UserServiceImpl
* @Author yuhuofei
* @Date 2022/6/14 22:02
* @Version 1.0
*/
public class UserServiceImpl implements UserService {
private UserDao userDao;
//利用 set 方法,按需变更userDao的值,实现值的动态注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.selectUser();
}
}
测试类的变更
import com.yuhuofei.dao.XiaoMingUserDaoImpl;
import com.yuhuofei.service.UserService;
import com.yuhuofei.service.UserServiceImpl;
/**
* @Description
* @ClassName TestIoc
* @Author yuhuofei
* @Date 2022/6/16 23:46
* @Version 1.0
*/
public class TestIoc {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
//新建小明这个对象
XiaoMingUserDaoImpl xiaoMingUserDao = new XiaoMingUserDaoImpl();
//设置UserServiceImpl类中的成员变量userDao的值
((UserServiceImpl) userService).setUserDao(xiaoMingUserDao);
//调用查询方法
userService.getUser();
}
}
执行测试类后,得到的结果如下

如果要继续查询其他人的用户信息,在 dao 层的实现类已经准备好的前提下,只需要改变测试类中 setUserDao() 方法的传参即可,查哪个人的,就传哪个人进去,将调用具体实现方法的控制权转移给了调用方。
IOC 设计思想说明
像上面这种,将调用具体实现方法的控制权由程序或者程序员手上转移给调用方的设计思想就是 IOC(控制反转)。对象的创建不再是由程序通过硬编码主动创建,而是由调用方进行传递,程序接收后,执行指定对象下的方法,获取结果。
这种设计思想,使得程序员不再需要去管理对象的创建,而程序要使用对象,接收调用方传递过来的就好,系统的耦合性进一步降低。
IOC (控制反转) 是一种设计思想,而DI (依赖注入) 只是它的一种实现方式。
Spring 过程:
控制反转也可以理解为一种方式,是一种通过描述( XML 或注解)并通过第三方去生产或获取特定对象的方式,Spring 中实现控制反转的是 IOC 容器,其实现方法是依赖注入(DI)。
IOC 的实现方式: