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
就会在创建对象时,自动从容器中找到每个成员变量对应的单实例并赋值。这里也引出了新的问题,如果被引用的实例还没创建该怎么办,循环引用了该怎么办。这些问题可以单独再写一篇,在此略过。
总结
通过IoC
和DI
,程序员的开发过程变成了:
- 开发每个模块,并通过
IoC
将模块实例化并放到容器中。 - 在需要引用模块的地方,声明模块实例的成员变量,并通过
@Autowire
关键字实现DI
。
如果不使用IoC
,那么程序员将难以知晓创建了多少实例,每个实例的状态,每个实例的生命周期。而IoC
将创建的实例统一放在了容器当中,相当于提供了一个控制台,可以很容易的获取每个实例的信息。如果不使用DI
,那么在每个类中调用某个模块的方法会十分繁琐。而通过成员变量
+
DI
的模式,可以清楚地读到每个类依赖了哪些模块,并简化开发流程。
通过IoC
和DI
,整个过程的耦合度大大降低,基于这种模式的开发也成为了最佳实践。