Spring提供的容器又称为IoC容器,什么是IoC?

    IoC全称Inversion of Control,直译为控制反转。那么何谓IoC?在理解IoC之前,我们先看看通常的Java组件是如何协作的。

    我们假定一个在线书店,通过获取书籍:

    为了从数据库查询书籍,BookService持有一个DataSource。为了实例化一个HikariDataSource,又不得不实例化一个HikariConfig

    现在,我们继续编写UserService获取用户:

    1. public class UserService {
    2. private HikariConfig config = new HikariConfig();
    3. private DataSource dataSource = new HikariDataSource(config);
    4. public User getUser(long userId) {
    5. try (Connection conn = dataSource.getConnection()) {
    6. ...
    7. return user;
    8. }
    9. }

    因为UserService也需要访问数据库,因此,我们不得不也实例化一个HikariDataSource

    在处理用户购买的CartServlet中,我们需要实例化UserServiceBookService

    类似的,在购买历史HistoryServlet中,也需要实例化UserServiceBookService

    1. public class HistoryServlet extends HttpServlet {
    2. private UserService userService = new UserService();
    • 实例化一个组件其实很难,例如,BookServiceUserService要创建HikariDataSource,实际上需要读取配置,才能先实例化HikariConfig,再实例化HikariDataSource

    • 没有必要让BookServiceUserService分别创建DataSource实例,完全可以共享同一个DataSource,但谁负责创建DataSource,谁负责获取其他组件已经创建的DataSource,不好处理。类似的,CartServletHistoryServlet也应当共享BookService实例和UserService实例,但也不好处理。

    • 很多组件需要销毁以便释放资源,例如DataSource,但如果该组件被多个组件共享,如何确保它的使用方都已经全部被销毁?

    • 随着更多的组件被引入,例如,书籍评论,需要共享的组件写起来会更困难,这些组件的依赖关系会越来越复杂。

    • 测试某个组件,例如BookService,是复杂的,因为必须要在真实的数据库环境下执行。

    从上面的例子可以看出,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

    因此,核心问题是:

    • 谁负责创建组件?
    • 谁负责根据依赖关系组装组件?

    传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:

    在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制,例如,BookService自己并不会创建,而是等待外部通过setDataSource()方法来注入一个DataSource

    不直接new一个DataSource,而是注入一个DataSource,这个小小的改动虽然简单,却带来了一系列好处:

    • BookService不再关心如何创建DataSource,因此,不必编写读取数据库配置之类的代码;
    • DataSource实例被注入到BookService,同样也可以注入到UserService,因此,共享一个组件非常简单;
    • 测试BookService更容易,因为注入的是DataSource,可以使用内存数据库,而不是真实的MySQL配置。 因此,IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

    因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现,例如:

    1. <beans>
    2. <bean id="dataSource" class="HikariDataSource" />
    3. <bean id="bookService" class="BookService">
    4. <property name="dataSource" ref="dataSource" />
    5. </bean>
    6. <bean id="userService" class="UserService">
    7. <property name="dataSource" ref="dataSource" />
    8. </bean>
    9. </beans>

    上述XML配置文件指示IoC容器创建3个JavaBean组件,并把id为dataSource的组件通过属性dataSource(即调用setDataSource()方法)注入到另外两个组件中。

    在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。

    我们从上面的代码可以看到,依赖注入可以通过set()方法实现。但依赖注入也可以通过构造方法实现。

    很多Java类都具有带参数的构造方法,如果我们把改造为通过构造方法注入,那么实现代码如下:

    Spring的IoC容器同时支持属性注入和构造方法注入,并允许混合使用。

    • 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

    IoC原理 - 图1