Spring IoC(一)-IoC基础

490 阅读4分钟

这是我参与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;
    }
}

在处理priceproductController中我们都需要这两个服务,那么代码就会变成这样:

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)IoCDI的一种实现,它将组件的创建+配置与组件的使用相分离,并且,由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组件,并把iddataSource的组件通过属性dataSource(即调用setDataSource()方法)注入到productService组件中。

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

Annotation

基于XML配置的方式虽然结构清晰,但是写起来繁琐,没新增一个bean都需要配置到XML中。所以Spring提供了注解式的配置方式,由Spring自动扫描并且装配。

@Component
public class ProductService {

    @Autowired
    private DataSource dataSource;
}