这是我参与8月更文挑战的第28天,活动详情查看: 8月更文挑战
什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。
通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JavaEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
IoC原理
IoC
全称Inversion of Control
,直译为控制反转。那么何谓IoC
?在理解IoC
之前,我们先看看通常的Java组件是如何协作的。我们假设一个商品服务,提供了查询商品信息的功能:
public class ProductService {
private HikariConfig hikariConfig = new HikariConfig();
private DataSource dataSource = new HikariDataSource(hikariConfig);
public Product getProductInfo(String productCode) {
Product product = null;
try (Connection connection = dataSource.getConnection()){
// ...
} catch (SQLException var) {
var.printStackTrace();
}
return product;
}
}
为了获取商品信息需要实例化HikariDataSource
,为了实例化HikariDataSource
不得不实例化一个HikariConfig
。同样的如果我们还有查询接个服务PriceService
等等,那么我们每次都需要重复实例化的操作。
public class PriceService {
private HikariConfig hikariConfig = new HikariConfig();
private DataSource dataSource = new HikariDataSource(hikariConfig);
public Price getPriceByProductCode(String productCode) {
Price price = null;
try (Connection connection = dataSource.getConnection()){
// ...
} catch (SQLException var) {
var.printStackTrace();
}
return price;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
在处理price
和product
的Controller
中我们都需要这两个服务,那么代码就会变成这样:
public class ProductController {
private PriceService priceService = new PriceService();
private ProductService productService = new ProductService();
// ...
}
public class PriceController {
private PriceService priceService = new PriceService();
private ProductService productService = new ProductService();
// ...
}
可以发现上述每个组件都采用了一种简单的通过new
创建实例并持有的方式,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。
因此出现了IOC
,准确的说应该是DI(Dependency Injection)
,IoC
是DI
的一种实现,它将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
传统的方式上面已经说了,控制权在程序本身,程序的控制流程也完全由程序员掌握。在IoC
模式下,控制权发生了反转,即从应用程序转移到了IoC
容器,所有组件不再由应用程序自己创建和配置,而是由IoC
容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC
容器中被“装配”出来,需要某种“注入”机制。例如,在ProductService
中,并不直接创建一个DataSource
,而是等外部调用setDataSource
方法注入一个DataSource
。
public class ProductService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
// ...
}
不直接new
一个DataSource
,而是注入一个DataSource
,这个小小的改动虽然简单,却带来了一系列好处:
-
ProductService
不再关心如何创建DataSource
,因此,不必编写读取数据库配置之类的代码。 -
DataSource
实例被注入到ProductService
,同样也可以注入到PriceService
,因此,共享一个组件非常简单。
因为IoC
容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现,例如:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"/>
<bean id="productService" class="*.ProductService">
<property name="dataSource" ref="dataSource"/>
</bean>
上述XML配置文件指示IoC容器创建2个JavaBean
组件,并把id
为dataSource
的组件通过属性dataSource
(即调用setDataSource()
方法)注入到productService
组件中。
在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。
Annotation
基于XML
配置的方式虽然结构清晰,但是写起来繁琐,没新增一个bean
都需要配置到XML
中。所以Spring提供了注解式的配置方式,由Spring自动扫描并且装配。
@Component
public class ProductService {
@Autowired
private DataSource dataSource;
}