SpringBoot-IoC-DI

在讨论Spring框架时,其中最核心的组件莫过于 Spring Core。而Spring Core完成了两个关键概念:IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)。

IoC

IoC是一种设计模式,它将对象的创建和管理的控制从应用程序代码转移到了容器中。在Java当中,为了提高代码的复用率和可维护性,我们采用模块化编程,将功能相关的代码抽取成一个类/模块。在本文中,模块专指提供方法,被其他类调用的类,本身也是一个类;而模块的调用类就称为类。

如果不采用IoC,使用某个模块的方法时,需要通过new关键字创造对象,再调用其中的方法,或者通过模块类直接调用其中的静态方法。这样在代码中需要显示地创建对象。并且如果一个模块在多个类中被使用时,会被重复创建多次,造成性能浪费。如果采用单实例,则需要通过构造函数显示地将实例传递进来。这在一个模块被多个类使用,一个类会使用多个模块的情况下变得十分繁琐。

而在IoC模式下,所有类的实例都会由容器统一创建并管理。容器本身很像HashMap,可以通过类名/类型等作为key,从容器中获取到value实例对象。当需要在某个类中使用某个模块的方法,不再需要手动创建,而是直接从容器中获取已经创建好的实例对象。这就是IoC创建对象实例的功能。实际上IoC常还被用来解决对象之间的依赖关系、处理对象的生命周期等,在此不赘述。

从描述中可以发现,每个模块的实例化时机也发生了变化。本来是在每次将被访问时,new一个实例;而IoC一般是在程序启动时就将所有模块进行实例化并放入容器。但这本身也可以通过配置项进行修改。

DI

在上文介绍中,IoC管理了实例的创建,并将所创实例统一用一个容器进行管理访问。那么每当需要使用某个模块时,操作变成了从容器中获取实例,再调用实例的方法。当一个类中有多个方法都使用同一个实例,这个实例会在每个方法中都需要获取一次。因为方法内定义的是局部变量,方法运行结束就会出栈。为了简化开发,可以将用到的实例都设置为成员变量,这样实例的作用域扩大到整个类,所有成员方法都能使用。额外一提,将实例设置为成员变量,只会记录实例的指针/引用,并不会新建一个实例,因此其开销是可接受的。这种将某个实例赋值给成员变量的过程,叫依赖注入。

但问题还存在,每个成员变量的初始化该怎么操作。可以通过直接在声明时赋值、使用代码块初始化,或者通过构造函数初始化。但无论使用哪种方式,为每个成员变量显示地从容器中根据类名/类型获取实例的代码不可避免。实际情况中,大部分实例在容器中都是单实例模式的,这意味着根据成员变量的类型,在容器中有且仅能获取到一个。

为了简化开发,Spring提供了@Autowire注解。只需要在每个实例成员变量上添加这个注解,Spring就会在创建对象时,自动从容器中找到每个成员变量对应的单实例并赋值。这里也引出了新的问题,如果被引用的实例还没创建该怎么办,循环引用了该怎么办。这些问题可以单独再写一篇,在此略过。

总结

通过IoCDI,程序员的开发过程变成了:

  1. 开发每个模块,并通过IoC将模块实例化并放到容器中。
  2. 在需要引用模块的地方,声明模块实例的成员变量,并通过@Autowire关键字实现DI

如果不使用IoC,那么程序员将难以知晓创建了多少实例,每个实例的状态,每个实例的生命周期。而IoC将创建的实例统一放在了容器当中,相当于提供了一个控制台,可以很容易的获取每个实例的信息。如果不使用DI,那么在每个类中调用某个模块的方法会十分繁琐。而通过成员变量 + DI的模式,可以清楚地读到每个类依赖了哪些模块,并简化开发流程。

通过IoCDI,整个过程的耦合度大大降低,基于这种模式的开发也成为了最佳实践。


SpringBoot-IoC-DI
http://dracoyus.github.io/2023/04/13/SpringBoot-IoC-DI/
作者
DracoYu
发布于
2023年4月13日
许可协议