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 容器,可独自进行测试,大大提高了开发效率。