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
中,咱们须要实例化UserService
和BookService
:
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
中,也须要实例化UserService
和BookService
:
public class HistoryServlet extends HttpServlet { private BookService bookService = new BookService(); private UserService userService = new UserService();}
上述每个组件都采纳了一种简略的通过new
创立实例并持有的形式。仔细观察,会发现以下毛病:
- 实例化一个组件其实很难,例如,
BookService
和UserService
要创立HikariDataSource
,实际上须要读取配置,能力先实例化HikariConfig
,再实例化HikariDataSource
。 - 没有必要让
BookService
和UserService
别离创立DataSource
实例,齐全能够共享同一个DataSource
,但谁负责创立DataSource
,谁负责获取其余组件曾经创立的DataSource
,不好解决。相似的,CartServlet
和HistoryServlet
也该当共享BookService
实例和UserService
实例,但也不好解决。 - 很多组件须要销毁以便开释资源,例如
DataSource
,但如果该组件被多个组件共享,如何确保它的应用方都曾经全副被销毁? - 随着更多的组件被引入,例如,书籍评论,须要共享的组件写起来会更艰难,这些组件的依赖关系会越来越简单。
- 测试某个组件,例如
BookService
,是简单的,因为必须要在实在的数据库环境下执行。
从下面的例子能够看出,如果一个零碎有大量的组件,其生命周期和相互之间的依赖关系如果由组件本身来保护,岂但大大增加了零碎的复杂度,而且会导致组件之间极为严密的耦合,继而给测试和保护带来了极大的艰难。
因而,外围问题是:
- 谁负责创立组件?
- 谁负责依据依赖关系组装组件?
- 销毁时,如何按依赖程序正确销毁?
解决这一问题的外围计划就是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
,这个小小的改变尽管简略,却带来了一系列益处:
BookService
不再关怀如何创立DataSource
,因而,不用编写读取数据库配置之类的代码;DataSource
实例被注入到BookService
,同样也能够注入到UserService
,因而,共享一个组件非常简单;- 测试
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的容器中运行。这种无侵入的设计有以下益处:
- 应用程序组件既能够在Spring的IoC容器中运行,也能够本人编写代码自行组装配置;
- 测试的时候并不依赖Spring容器,可独自进行测试,大大提高了开发效率。