IOC 是 Inversion of Control 的简写,译为“控制反转”,Spring 通过 IOC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IOC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
1 控制反转(IOC)
抛开Spring等框架,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用,那么调用类掌握着被调用类的控制权。
在 Spring 应用中,Java 对象创建的控制权是掌握在 IOC 容器手里的,也就是说 IOC 容器掌握着这些被调用类的控制权。其步骤如下:
开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 <bean> 标签、在 Java 类上使用 @Component 注解等。
Spring 启动时, IOC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
当我们想要使用某个 Bean 时,可以直接从 IOC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
IOC 使得应用开发从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IOC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IOC容器创建它所需要的对象(Bean)。
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”,它最大的优势在于解耦合。
1.1 IOC 的工作原理
软件开发过程中,各对象和模块间不可避免地存在一定地耦合关系(比如B调用A,C调用B,A调用C),若一个系统的耦合度过高,那么就会造成难以维护的问题,但完全没有耦合的代码几乎无法完成任何工作。因此我们在程序设计时,所秉承的思想一般都是在不影响系统功能的前提下,最大限度的降低耦合度。
IoC 底层通过工厂模式、Java 的反射机制、XML 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下:
在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
我们可以把 IOC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
IoC 利用 Java 的反射机制,根据类名生成相应的对象(即 Spring Bean),并根据依赖关系将这个对象注入到依赖它的对象中。
由于对象的基本信息、对象之间的依赖关系都是在配置文件中定义的,并没有在代码中紧密耦合,因此即使对象发生改变,IOC都会自动在配置文件中进行修改, Java 代码(涉及调用关系)则不需要变动,这就是 Spring IoC 实现解耦的原理。
1.2 以@Component举例
关于实现控制反转,将类Spring Bean注入到IOC容器中,主要有以下注解:
@controller :用于标注控制层;
@service :用于标注服务层,主要用来进行业务的逻辑处理;
@repository:用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件;
@component:用于标注各种组件,若该类不同于以上归类,可使用该注解。
那么如果想将对应的Spring Bean从IOC容器中取出来,一般是用 @Autowired或 @Resourse注解,这将在依赖注入部分进行举例。
首先创建一个类,然后以new形式和@Component注解形式分别解释
① 创建Called类
编写该类的无参构造,若该类被调用,就会在控制台上输出"该类被创建成功"
public class Called {
public Called(){
System.out.println("该类被创建成功");
}
}
② new方式创建对象
我们在Call类中的main方法中new一个Called对象
public class Call {
public static void main(String[] args) {
Called called = new Called();
}
}
执行main方法,输出成功,说明Called对象创建成功👇
③ 给Called类标注@Component注解
@Component
public class Called {
public Called(){
System.out.println("该类被创建成功");
}
}
④ 执行项目启动类
控制台输出成功,说明项目启动后Called类已作为Spring Bean注入Spring的IOC容器👇
2 依赖注入(DI)
依赖注入(Denpendency Injection,简写为 DI)是 Martin Fowler 在 2004 年在对“控制反转”进行解释时提出的。Martin Fowler 认为“控制反转”一词很晦涩,无法让人很直接的理解“到底是哪里反转了”,因此他建议使用“依赖注入”来代替“控制反转”。
在面向对象中,对象和对象之间是存在一种叫做“依赖”的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象,比如A调用B,那么可以说A依赖B。
依赖注入本质上是Spring Bean属性注入的一种,只不过这个属性是一个对象属性而已。
我在这边加大点难度,以“调用类->接口->实现类(最终的被调用方)“形式,可能需要一点关于接口的java基础。
2.1 单接口单实现类
顾名思义,调用类调用接口的方法,且该方法只被一个实现类实现
① 创建CalledInterface接口
在里边写一个抽象方法
public interface CalledInterface {
String getString();
}
② 使Called类实现该接口
也重写了getString抽象方法
@Component
public class Called implements CalledInterface{
public Called(){
System.out.println("该类被创建成功");
}
@Override
public String getString() {
return "getString 方法被调用";
}
}
③ Call类注入该接口并作为控制层
以@Autowired为例
@RestController
public class Call {
@Autowired
private CalledInterface calledInterface;
@RequestMapping("/call")
public String call() {
return calledInterface.getString();
}
}
④ 执行项目启动类并访问该接口
输出成功,说明注入成功。
2.2 单接口多实现类
如果该接口有多个实现类呢,该如何处理?
首先增加另一个实现类试试:
@Component
public class CalledAnother implements CalledInterface{
@Override
public String getString() {
return "CalledAnother 的 getString 方法被调用";
}
}
然后执行启动类,可以看到项目报错了👇,说明系统不知道该调用哪个实现类的getString()方法。
主要有以下几种解决方法:
@Qualifier("类名"):@Autowired + @Qualifier("类名") 指定具体实现类(用在控制层)
@Resource(name = "类名"):这也可以实现(用在控制层)
@Primary:表示该实现类是主实现类,默认使用该类方法。(用在实现类)
而且可以使用 @Component("自定义类名") 和 @Service("自定义类名") 在实现类上自定义类名。
这里以@Resource(name = "类名")和@Service("自定义类名")为例:
① Called类使用@Service("自定义类名")
@Service("自定义类名")
public class Called implements CalledInterface{
public Called(){
System.out.println("该类被创建成功");
}
@Override
public String getString() {
return "Called 的 getString 方法被调用";
}
}
② Call类使用@Resource(name = "类名")
@RestController
public class Call {
@Resource(name = "自定义类名")
private CalledInterface calledInterface;
@RequestMapping("/call")
public String call() {
return calledInterface.getString();
}
}
③ 执行项目启动类并访问该接口
说明指定具体实现类成功,其实还有好几种搭配方式,感兴趣的小伙伴可以自己尝试一下~
参考文章
1.Spring IoC(控制反转) (biancheng.net)
2.控制反转与依赖注入_望天边星宿的博客-CSDN博客