IOC容器

在学习Spring框架时,咱们遇到的第一个也是最外围的概念就是容器。

什么是容器?容器是一种为某种特定组件的运行提供必要反对的一个软件环境。例如,Tomcat就是一个Servlet容器,它能够为Servlet的运行提供运行环境。相似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux过程。

通常来说,应用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连贯,解析HTTP协定等非常复杂的服务,如果没有容器来提供这些服务,咱们就无奈编写像Servlet这样代码简略,功能强大的组件。晚期的JavaEE服务器提供的EJB容器最重要的性能就是通过申明式事务服务,使得EJB组件的开发人员不用本人编写简短的事务处理代码,所以极大地简化了事务处理。

Spring的外围就是提供了一个IoC容器,它能够治理所有轻量级的JavaBean组件,提供的底层服务包含组件的生命周期治理、配置和组装服务、AOP反对,以及建设在AOP根底上的申明式事务服务等。

本章咱们探讨的IoC容器,次要介绍Spring容器如何对组件进行生命周期治理和配置组装服务。

IOC原理

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

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

咱们假设一个在线书店,通过BookService获取书籍:

public class BookService {    private HikariConfig config = new HikariConfig();    private DataSource dataSource = new HikariDataSource(config);    public Book getBook(long bookId) {        try (Connection conn = dataSource.getConnection()) {            ...            return book;        }    }}

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

当初,咱们持续编写UserService获取用户:

public class UserService {    private HikariConfig config = new HikariConfig();    private DataSource dataSource = new HikariDataSource(config);    public User getUser(long userId) {        try (Connection conn = dataSource.getConnection()) {            ...            return user;        }    }}

因为UserService也须要拜访数据库,因而,咱们不得不也实例化一个HikariDataSource

在解决用户购买的CartServlet中,咱们须要实例化UserServiceBookService

public class CartServlet extends HttpServlet {    private BookService bookService = new BookService();    private UserService userService = new UserService();    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        long currentUserId = getFromCookie(req);        User currentUser = userService.getUser(currentUserId);        Book book = bookService.getBook(req.getParameter("bookId"));        cartService.addToCart(currentUser, book);        ...    }}

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

public class HistoryServlet extends HttpServlet {    private BookService bookService = new BookService();    private UserService userService = new UserService();}

上述每个组件都采纳了一种简略的通过new创立实例并持有的形式。仔细观察,会发现以下毛病:

  1. 实例化一个组件其实很难,例如,BookServiceUserService要创立HikariDataSource,实际上须要读取配置,能力先实例化HikariConfig,再实例化HikariDataSource
  2. 没有必要让BookServiceUserService别离创立DataSource实例,齐全能够共享同一个DataSource,但谁负责创立DataSource,谁负责获取其余组件曾经创立的DataSource,不好解决。相似的,CartServletHistoryServlet也该当共享BookService实例和UserService实例,但也不好解决。
  3. 很多组件须要销毁以便开释资源,例如DataSource,但如果该组件被多个组件共享,如何确保它的应用方都曾经全副被销毁?
  4. 随着更多的组件被引入,例如,书籍评论,须要共享的组件写起来会更艰难,这些组件的依赖关系会越来越简单。
  5. 测试某个组件,例如BookService,是简单的,因为必须要在实在的数据库环境下执行。

从下面的例子能够看出,如果一个零碎有大量的组件,其生命周期和相互之间的依赖关系如果由组件本身来保护,岂但大大增加了零碎的复杂度,而且会导致组件之间极为严密的耦合,继而给测试和保护带来了极大的艰难。

因而,外围问题是:

  1. 谁负责创立组件?
  2. 谁负责依据依赖关系组装组件?
  3. 销毁时,如何按依赖程序正确销毁?

解决这一问题的外围计划就是IoC。

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

CartServlet创立了BookService,在创立BookService的过程中,又创立了DataSource组件。这种模式的毛病是,一个组件如果要应用另一个组件,必须先晓得如何正确地创立它。

在IoC模式下,控制权产生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序本人创立和配置,而是由IoC容器负责,这样,应用程序只须要间接应用曾经创立好并且配置好的组件。为了能让组件在IoC容器中被“拆卸”进去,须要某种“注入”机制,例如,BookService本人并不会创立DataSource,而是期待内部通过setDataSource()办法来注入一个DataSource

public class BookService {    private DataSource dataSource;    public void setDataSource(DataSource dataSource) {        this.dataSource = dataSource;    }}

不间接new一个DataSource,而是注入一个DataSource,这个小小的改变尽管简略,却带来了一系列益处:

  1. BookService不再关怀如何创立DataSource,因而,不用编写读取数据库配置之类的代码;
  2. DataSource实例被注入到BookService,同样也能够注入到UserService,因而,共享一个组件非常简单;
  3. 测试BookService更容易,因为注入的是DataSource,能够应用内存数据库,而不是实在的MySQL配置。

因而,IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最次要的问题:将组件的创立+配置与组件的应用相拆散,并且,由IoC容器负责管理组件的生命周期。

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

<beans>    <bean id="dataSource" class="HikariDataSource" />    <bean id="bookService" class="BookService">        <property name="dataSource" ref="dataSource" />    </bean>    <bean id="userService" class="UserService">        <property name="dataSource" ref="dataSource" />    </bean></beans>

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

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

依赖注入形式

咱们从下面的代码能够看到,依赖注入能够通过set()办法实现。但依赖注入也能够通过构造方法实现。

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

public class BookService {    private DataSource dataSource;    public BookService(DataSource dataSource) {        this.dataSource = dataSource;    }}

Spring的IoC容器同时反对属性注入和构造方法注入,并容许混合应用。

无侵入容器

在设计上,Spring的IoC容器是一个高度可扩大的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件基本不晓得本人在Spring的容器中运行。这种无侵入的设计有以下益处:

  1. 应用程序组件既能够在Spring的IoC容器中运行,也能够本人编写代码自行组装配置;
  2. 测试的时候并不依赖Spring容器,可独自进行测试,大大提高了开发效率。