我对Spring IoC的一些理解

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

在前一篇文章Spring 快速开始中,使用了下面的代码来实例化Bean对象,使用了一个ApplicationContext类,并没有使用new关键字。实例化Bean对象的过程交给了Spring,而无需开发者显式的使用new等方法去实现,这就是SpringIoC设计思想。

public class HelloWorldTest {

    @Test
    public void testHelloWorld() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        HelloWorld helloWorld = (HelloWorld) applicationContext.getBean("helloWorld");
        helloWorld.helloWorld();
    }
}
复制代码

IoC的简单介绍

IoC(Inverse of Control)即控制反转,它是一种设计思想,由于出现的时间较晚,所以没有包含在GoF中。它的设计思想是采用依赖注入的技术,将对象的控制权限(创建,销毁)交由IoC容器,调用者通过配置文件(如XML)来获取对象。
Spring framework则是采用的IoC设计思想,Spring IoC容器通过读取配置元数据,获取有关类实例化,配置和组装对象的描述信息,在Spring中,配置元数据支持XMLJava注解,Java代码实现。前面示例就是使用的XML来描述配置元数据。


IoC 容器

ApplicationContext

在前面示例中,构造了一个ApplicationContext对象,然后通过调用getBean()方法获取到HelloWorld对象。ApplicationContext是什么呢?单从名称上看ApplicationContext是应用程序上下文。

image.png 可以看它继承了BeanFactory,看到BeanFactory便能想到熟悉的抽象工厂模式(Abstract Factory Mode),Spring IoC容器也是使用了抽象工厂模式来创建Bean对象。ApplicationContext就是Spring IoC容器的代表实现,它扩展了BeanFactory,并具备BeanFactory的所有功能,且在其基础上增加了用于国际化的消息资源处理,应用层特定的上下文等。 ApplicationContext的子接口实现类如下图: image.png

子类FileSystemXmlApplicationContextClassPathXmlApplicationContext都是从XML配置文件中获取配置元数据。

ClassPathXmlApplicationContext

ClassPathXmlApplicationContext是从类路径上加载XML配置文件。下面的代码是实现一个账户转账的例子:

public class TransferServiceImpl implements TransferService {

    private ApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");

    private AccountDao accountDao = context.getBean("accountDao", AccountDao.class);

    @Override
    public void transfer(String fromAccountNum, String toAccountNum, double money) {
        Account fromAccount = accountDao.getAccountByNumber(fromAccountNum);
        Account toAccount = accountDao.getAccountByNumber(toAccountNum);
        fromAccount.setBalance(fromAccount.getBalance() - money);
        System.out.println(fromAccount.getAccountNumber() + " " + fromAccount.getBalance());
        toAccount.setBalance(toAccount.getBalance() + money);
        System.out.println(toAccount.getAccountNumber() + " " + toAccount.getBalance());
        accountDao.updateAccountByNumber(fromAccount);
        accountDao.updateAccountByNumber(toAccount);
    }
    
}
复制代码

在项目的resources目录下创建了docs.xml用来配置bean信息。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="accountDao" class="com.linnanc.dao.AccountDao"></bean>
</beans>
复制代码

项目编译完成后会在target/classes生成docs.xmlclasses也就是ClassPath。在docs.xmlbean标签里面有两个属性idclass。在BeanFactory中有如下两个getBean()方法:

public interface BeanFactory {
    // ......
    Object getBean(String name) throws BeansException;
    
    <T> T getBean(Class<T> requiredType) throws BeansException;
    // ......
}
复制代码

Object getBean(String name)传入的参数nameid的值,id是用来唯一标识bean的属性,<T> T getBean(Class<T> requiredType)传入的requiredTypeclass的值,class属性是定义了bean的类型,使用的是全限定类名。下面的语句是通过AccountDao.class获取bean

private AccountDao accountDao = context.getBean(AccountDao.class);
复制代码

AccountDao是一个接口,

public interface AccountDao {

    /**
     * 根据卡号查找 Account
     * @param accountNumber
     * @return
     */
    Account getAccountByNumber(String accountNumber);

    /**
     * 更新相应卡号的账户信息
     * @param account
     */
    void updateAccountByNumber(Account account);
}
复制代码

这种通过接口的类型获取bean的方式,当接口有多个实现类时,则无法确定要获取的是哪一个bean,将抛出异常org.springframework.beans.factory.NoUniqueBeanDefinitionException。当接口有多个实现类时就需要传入具体的实现类。

private AccountDao accountDao = context.getBean(AccountDaoImpl.class);
复制代码

FileSystemXmlApplicationContext

FileSystemXmlApplicationContext则是从文件系统中查找xml配置文件,它的默认路径是项目的根路径。此时需要指明daos.xml相对于项目根路径的路径,如下:

private ApplicationContext context = new FileSystemXmlApplicationContext("target/classes/daos.xml");
复制代码

代码:github.com/linnanc/spr…


Bean

Spring官方文档Bean Overview一节中,Bean定义有NameClassScopeConstructor argumentsPropertiesAutowiring modeLazy initialization modeInitialization methodDestruction method这些属性。

Name

在前面使用xml配置文件中,有用到id属性,id属性可以唯一的标识一个beanname属性和id属性一样,都可以唯一的标识bean。如果没有显式指定idname,容器会给bean生成唯一的名称。通过getBeanDefinitionNames()可以获取配置文件中所有的bean名称。

@Test
public void testGetBeanName() {
    ApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");

    String[] names = context.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }

}
复制代码

Class

<bean>标签里面还有一个class属性,class就是bean的类型,实例化bean时就要用到该属性。实例化bean有三种方式。分别是无参构造方法实例化,静态工厂方法实例化,普通工厂方法实例化。

使用无参的构造方法实例化

之前的示例中,bean的构造方法都是无参的,实例化这些bean使用的是默认无参构造方法实例化的方式。在AccountDaoImpl中增加一个有参构造方法,测试bean实例化。

public class AccountDaoImpl implements AccountDao {

    /**
     * 添加一个有参构造方法,测试无参构造方法实例化
     * @param n
     */
    public AccountDaoImpl(int n) {
    }
    // ......
}
复制代码

抛出下面的异常

Caused by: java.lang.NoSuchMethodException: com.linnanc.dao.impl.AccountDaoImpl.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
复制代码

这个异常是没有获取到构造方法,由于添加了一个新的构造方法,编译器不会再为类生成无参的构造方法了。因此出现了该异常。

使用静态工厂方法实例化

使用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。Spring将调用此方法返回实例对象。
下面的代码实现一个工厂类,它有一个createAccountDao的静态方法,返回一个AccountDaoImpl对象。


public class AccountDaoFactory {

    /**
     * 静态工厂方法
     * @return
     */
    public static AccountDao createAccountDao() {
        return new AccountDaoImpl();
    }
}
复制代码

再在Spring配置文件中,指定工厂方法

<bean id="accountDaoFactory" class="com.linnanc.dao.impl.AccountDaoFactory" factory-method="createAccountDao"></bean>
复制代码

这样Spring容器将会调用createAccountDao来创建一个AccountDaoImpl对象。

使用实例工厂方法实例化

使用实例工厂方法实例化与通过静态工厂方法实例化类似,要使用这种方式实例化对象,beanclass属性要保存为空,并在factory-bean属性中指定当前(或父级或祖先)容器中bean的名称,该容器包含要调用一创建对象的实例化方法。使用factory-method属性设置工厂方法本身的名称。

public class AccountDaoFactory {

    /**
     * 普通工厂方法
     * @return
     */
    public AccountDao createAccountDao2() {
        return new AccountDaoImpl();
    }
}
复制代码

xml中将factory—bean属性中指定当前容器中bean的名称。

<bean id="accountDaoFactory" class="com.linnanc.dao.impl.AccountDaoFactory"></bean>
<bean id="accountDao2" factory-bean="accountDaoFactory" factory-method="createAccountDao2"></bean>
复制代码

Scope

Scopebean的作用范围。Spring框架中支持六种作用范围,有四种作用范围是当使用基于WebApplicationContext才有效的,也可以自定义作用范围。

作用域 描述
singleton (默认)每一个Spring IoC容器都拥有唯一的实例对象
prototype 一个bean定义可以创建任意多个实例对象
request 将单个bean定义范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。只有基于WebApplicationContext才可用
session 将单个bean定义范围限定为HTTP session的生命周期。只有基于WebApplicationContext才可用
application 将单个bean定义范围限定为ServletContext的生命周期。只有基于WebApplicationContext的才可用。
websocket 将单个bean定义范围限定为WebSocket的生命周期,只有基于WebApplicationContext才可用

单例作用域

单例bean在全局只有一个共享的实例,所有依赖单例bean的场景,容器返回的都是同一个实例。下面的代码展示了在一个Spring容器中,使用单例bean创建出来的对象都是同一个。

@Test
public void testBeanScope01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");
    AccountDao accountDao = (AccountDao) context.getBean("accountDao");
    System.out.println(accountDao);
    AccountDao accountDao1 = (AccountDao) context.getBean("accountDao");
    System.out.println(accountDao1);
}
复制代码

输出如下:

com.linnanc.dao.impl.AccountDaoImpl@2c039ac6
com.linnanc.dao.impl.AccountDaoImpl@2c039ac6
复制代码

需要注意的是Spring单例bean的概念不同于GoF设计模式中的单例,设计模式中的单例模式是在一个ClassLoader中只有一个实例,Spring中的单例,则是在整个Spring容器中只有一个单例对象。如果是不同的容器,则会存在多个不同的实例。下面的代码展示了不同的Spring容器中的单例bean不是同一个对象。
scope配置为singleton

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton"></bean>
复制代码

创建两个ApplicationContext对象,但使用的配置文件是同一个

@Test
public void testBeanScope02() {
    ApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");
    AccountDao accountDao = (AccountDao) context.getBean("accountDao");
    System.out.println(accountDao);
    ApplicationContext context1 = new ClassPathXmlApplicationContext("daos.xml");
    AccountDao accountDao1 = (AccountDao) context1.getBean("accountDao");
    System.out.println(accountDao1);
}
复制代码

输出如下:

com.linnanc.dao.impl.AccountDaoImpl@2c039ac6
com.linnanc.dao.impl.AccountDaoImpl@42d8062c
复制代码

可见并不是同一个对象。

  • 单例bean的生命周期

Spring会完整的管理的单例bean的生命周期,初始化,配置,装载,销毁单例bean都由Spring管理。单例bean在创建Spring容器时就已经被实例化,容器销毁时被销毁。

原型作用域

property原型bean是多实例的,每次调用getBean()方法来获取bean对象获得的都是一个新的bean实例。例如下面代码只将scope修改为prototype,然后运行之前获取单例bean的测试用例

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="prototype"></bean>
复制代码
@Test
public void testBeanScope01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");
    AccountDao accountDao = (AccountDao) context.getBean("accountDao");
    System.out.println(accountDao);
    AccountDao accountDao1 = (AccountDao) context.getBean("accountDao");
    System.out.println(accountDao1);
}
复制代码

输出如下:

com.linnanc.dao.impl.AccountDaoImpl@2c039ac6
com.linnanc.dao.impl.AccountDaoImpl@587d1d39
复制代码
  • 原型bean的生命周期

原型bean的生命周期与单例bean不同,Spring不会完整地管理原型bean的生命周期,原型bean在获取时才被实例化,Spring容器在初始化,配置和装载原型bean之后,便不会管理它了,它不会因为容器被销毁而销毁,所以使用原型bean时需要注意释放原型bean所持有的资源。这可以通过定义bean post-processorbean后置处理器)来处理。

依赖注入

通常一个应用中都不会只有一个对象,例如前面的TransferServiceImpl要依赖AccountDao才能工作,前面的代码中使用AccountDao是手动调用getBean()Spring容器中获取的。通过DI(依赖注入)可以让对象只通过构造参数,工厂方法的参数或者配置属性来获取依赖对象。容器会在创建bean的时候自动的注入依赖,无需再手动的调用getBean()获取,这个过程将开发者自己控制实例化bean交给了Spring容器,所以也被称为控制反转(IoC)。

依赖注入的方式

bean的依赖注入方式有两种:基于构造方法和基于setter方法。

基于构造方法的依赖注入(Constructor arguments

基于构造方法依赖注入使用的是BeanConstructor arguments属性,要在<bean>标签中配置<constructor-arg>子标签,如下:

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton"></bean>
<bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton">
    <constructor-arg name="accountDao" ref="accountDao"/>
</bean>
复制代码

TransferServiceImpl中,将accountDao传入构造方法

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao;

    public TransferServiceImpl(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transfer(String fromAccountNum, String toAccountNum, double money) {
        Account fromAccount = accountDao.getAccountByNumber(fromAccountNum);
        Account toAccount = accountDao.getAccountByNumber(toAccountNum);
        fromAccount.setBalance(fromAccount.getBalance() - money);
        System.out.println(fromAccount.getAccountNumber() + " " + fromAccount.getBalance());
        toAccount.setBalance(toAccount.getBalance() + money);
        System.out.println(toAccount.getAccountNumber() + " " + toAccount.getBalance());
        accountDao.updateAccountByNumber(fromAccount);
        accountDao.updateAccountByNumber(toAccount);
    }

}
复制代码

测试:

public class TransferServiceTest {

    @Test
    public void testTransfer01() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("daos.xml");
        TransferService transferService = (TransferService) applicationContext.getBean("transferService");
        String fromAccountNumber = "123456789";
        String toAccountNumber = "987654321";
        transferService.transfer(fromAccountNumber, toAccountNumber, 100);
    }

}
复制代码

上面的例子中ref是用来引用另外一个bean的,ref的值必须是一个bean。当需要引用bean时,被引用的bean会先实例化,然后配置属性。如果依赖的属性是一个基本类型或String呢?当属性是基本类型或String的时候可以使用type属性显式指定参数类型,并且使用value属性指定其值。参考constructor-based Dependency Injection一节的例子。

基于setter方法的依赖注入(Properties

基于setter方法依赖注入使用的是BeanProperties属性,要在<bean>标签中配置<property>子标签,例如要在TransferServiceImpl中增加一个TradingRecordDao用来记录每笔交易,使用setter注入如下:

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton"></bean>
<bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="singleton"></bean>
<bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton">
    <constructor-arg name="accountDao" ref="accountDao"/>
    <property name="tradingRecordDao" ref="tradingRecordDao"/>
</bean>
复制代码

TransferServiceImpl中增加setter方法,如下:

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao;

    private TradingRecordDao tradingRecordDao;

    public TransferServiceImpl(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setTradingRecordDao(TradingRecordDao tradingRecordDao) {
        this.tradingRecordDao = tradingRecordDao;
    }
    
    // ...
}
复制代码

AccountDao是使用构造方法注入的,TradingRecordDao是使用setter注入的,Spring同时支持基于构造方法和setter方法的依赖注入。

选择基于构造方法还是基于setter方法?

由于可以混合使用基于构造方法和基于setterDI,对于必要的依赖使用构造方法,对于可选依赖使用setter方法。注意,在setter方法上使用@Required注解可以使属性成为required依赖。

使用p命名空间的依赖注入

p命名空间本质上还是基于setter方法的依赖注入,它可以简化xml配置。
在配置文件中引入p命名空间:

xmlns:p="http://www.springframework.org/schema/p"
复制代码

修改注入方式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton"></bean>
    <bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="singleton"></bean>
    <bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton" p:tradingRecordDao-ref="tradingRecordDao">
        <constructor-arg name="accountDao" ref="accountDao"/>
<!--        <property name="tradingRecordDao" ref="tradingRecordDao"/>-->
    </bean>
</beans>
复制代码

依赖注入的数据类型

依赖注入的方式一节中有描述基本数据类型和引用数据类型。Spring还为Java集合类型ListSetMapProperties配置了依赖注入。
例如:

<bean id="myDataSource" class="com.linnanc.examples.MyDataSource"></bean>
<bean id="moreComplexObject" class="com.linnanc.examples.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected]</prop>
            <prop key="support">[email protected]</prop>
            <prop key="development">[email protected]</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>
复制代码
public class ComplexObject {

    private Properties adminEmails;
    private List<Object> someList;
    private Map<Object, Object> someMap;
    private Set<Object> someSet;

    public void setAdminEmails(Properties adminEmails) {
        this.adminEmails = adminEmails;
    }

    public void setSomeList(List<Object> someList) {
        this.someList = someList;
    }

    public void setSomeMap(Map<Object, Object> someMap) {
        this.someMap = someMap;
    }

    public void setSomeSet(Set<Object> someSet) {
        this.someSet = someSet;
    }

    public Properties getAdminEmails() {
        return adminEmails;
    }

    public List<Object> getSomeList() {
        return someList;
    }

    public Map<Object, Object> getSomeMap() {
        return someMap;
    }

    public Set<Object> getSomeSet() {
        return someSet;
    }
}
复制代码

参考

Autowiring mode

Spring容器可以根据bean之间的依赖关系自动装配,在<bean>标签中配置属性autowire,将bean设置为自动装配模式。自动装配功能有四种模式,开发者可以指定每个bean的自动装配模式。

模式 说明
no (默认)不自动装配,bean引用需要由ref元素定义
byName 按属性名自动装配,Spring会根据name查找bean并自动装配
byType 按类型自动装配,Spring会根据类型来查找bean,如果当前容器存在多个相同类型的bean则会抛出异常
constructor 类似于byType,应用于构造函数参数
使用byName自动装配

byName自动装配使用的是基于setter方法的方式进行依赖注入,所以要为依赖注入的属性提供setter方法。如下:

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao;

    private TradingRecordDao tradingRecordDao;

    @Override
    public void transfer(String fromAccountNum, String toAccountNum, double money) {
        Account fromAccount = accountDao.getAccountByNumber(fromAccountNum);
        Account toAccount = accountDao.getAccountByNumber(toAccountNum);
        fromAccount.setBalance(fromAccount.getBalance() - money);
        System.out.println(fromAccount.getAccountNumber() + " " + fromAccount.getBalance());
        toAccount.setBalance(toAccount.getBalance() + money);
        System.out.println(toAccount.getAccountNumber() + " " + toAccount.getBalance());
        accountDao.updateAccountByNumber(fromAccount);
        accountDao.updateAccountByNumber(toAccount);
    }
    
    /**
     * 为依赖注入的属性提供 setter 方法
     */
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setTradingRecordDao(TradingRecordDao tradingRecordDao) {
        this.tradingRecordDao = tradingRecordDao;
    }
}
复制代码

xml配置文件中,指定<bean>autowire属性为byName,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton" autowire="byName"></bean>
    <bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="singleton" autowire="byName"></bean>
    <bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton" autowire="byName"></bean>

</beans>
复制代码
使用byType自动装配

autowire改为byType,如下,可以成功自动装配

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton" autowire="byType"></bean>
<bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="singleton" autowire="byType"></bean>
<bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton" autowire="byType"></bean>
复制代码

增加一个相同类型的bean测试:

<bean id="accountDao2" name="accountDaoName2" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton" autowire="byType"></bean>
复制代码

抛出org.springframework.beans.factory.NoUniqueBeanDefinitionException异常。

使用constructor自动装配

constructor自动装配使用的是基于构造方法的方式进行依赖注入,在构造方法参数中要添加所有依赖注入的属性。如下:

public class TransferServiceImpl implements TransferService {

    private AccountDao accountDao;

    private TradingRecordDao tradingRecordDao;

    public TransferServiceImpl(AccountDao accountDao, TradingRecordDao tradingRecordDao) {
        this.accountDao = accountDao;
        this.tradingRecordDao = tradingRecordDao;
    }
    // ......
}
复制代码

autowire配置为constructor,如下:

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="singleton" autowire="constructor"></bean>
<bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="singleton" autowire="constructor"></bean>
<bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton" autowire="constructor"></bean>
复制代码
自动装配的局限与缺点
  • 使用自动装配无法为一些简单属性如基本类型,String,数组等进行自动装配。
  • 自动装配比显式的配置更容易产生歧义。
  • Spring容器生成文档的工具可能无法有效的提取自动装配的信息。
  • 使用byType或者constructor进行自动装配如果存在多个同类型bean会抛出异常。

由于上述的缺陷,可以将属性autowire-candidate设置为falsebean从自动装配中移除。

方法注入

当一个单例bean依赖于原型bean时,由于单例beanSpring启动时便已实例化完成,接下来再获取bean并不会重新实例化,因此尽管依赖的属性是原型bean,由于没有重新装配属性,所以依赖的原型bean还是同一个。例如:

<bean id="accountDao" name="accountDaoName" class="com.linnanc.dao.impl.AccountDaoImpl" scope="prototype" autowire="byName"></bean>
<bean id="tradingRecordDao" name="tradingRecordDao" class="com.linnanc.dao.impl.TradingRecordDaoImpl" scope="prototype" autowire="byName"></bean>
<bean id="transferService" class="com.linnanc.service.impl.TransferServiceImpl" scope="singleton" autowire="byName"></bean>
复制代码

transferService是一个单例bean,它依赖的属性accountDao是一个原型bean,下面的代码两次获取原型bean accountDao

@Test
public void testTransfer02() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("daos.xml");
    TransferServiceImpl transferService = (TransferServiceImpl) applicationContext.getBean("transferService");
    AccountDao accountDao = transferService.getAccountDao();
    System.out.println(accountDao);

    TransferServiceImpl transferService1 = (TransferServiceImpl) applicationContext.getBean("transferService");
    AccountDao accountDao1 = transferService1.getAccountDao();
    System.out.println(accountDao1);
}
复制代码

输出如下:

com.linnanc.dao.impl.AccountDaoImpl@ed9d034
com.linnanc.dao.impl.AccountDaoImpl@ed9d034
复制代码

获取到的都是同一个bean
如果想要获取到不同的原型bean,一种解决方案是放弃IoC,可以通过实现ApplicationContextAware接口让bean transferServiceApplicationContext可见,从而通过调用getBean("accountDao")来在bean transferService需要新的实例时来获取新的accountDao实例。例如:

public class TransferServiceImpl implements TransferService, ApplicationContextAware {

    private AccountDao accountDao;

    private TradingRecordDao tradingRecordDao;

    private ApplicationContext applicationContext;

    public AccountDao getAccountDao() {
        return (AccountDao) applicationContext.getBean("accountDao");
    }

    public TradingRecordDao getTradingRecordDao() {
        return tradingRecordDao;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setTradingRecordDao(TradingRecordDao tradingRecordDao) {
        this.tradingRecordDao = tradingRecordDao;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
    }
}
复制代码

上面的代码尽管实现了获取不同的原型bean,但是业务代码与Spring框架耦合在了一起。Spring IoC提供了方法注入来处理这种问题。
配置lookup-method为方法getAccountDao,如下:

<bean id="transferService1" class="com.linnanc.service.impl.TransferServiceImpl1" scope="singleton" autowire="byName">
    <lookup-method name="getAccountDao" bean="accountDao"/>
</bean>
复制代码

Spring将会使用CGLIB字节码技术来生成动态的子类重写getAccountDao方法实现注入。

public class TransferServiceImpl1 implements TransferService {

    private AccountDao accountDao;

    private TradingRecordDao tradingRecordDao;

    @Override
    public void transfer(String fromAccountNum, String toAccountNum, double money) {
        Account fromAccount = accountDao.getAccountByNumber(fromAccountNum);
        Account toAccount = accountDao.getAccountByNumber(toAccountNum);
        fromAccount.setBalance(fromAccount.getBalance() - money);
        System.out.println(fromAccount.getAccountNumber() + " " + fromAccount.getBalance());
        toAccount.setBalance(toAccount.getBalance() + money);
        System.out.println(toAccount.getAccountNumber() + " " + toAccount.getBalance());
        accountDao.updateAccountByNumber(fromAccount);
        accountDao.updateAccountByNumber(toAccount);
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setTradingRecordDao(TradingRecordDao tradingRecordDao) {
        this.tradingRecordDao = tradingRecordDao;
    }

    public AccountDao getAccountDao() {
        return accountDao;
    }

    public TradingRecordDao getTradingRecordDao() {
        return tradingRecordDao;
    }
}
复制代码

如果accountDao是单例bean,则每次获取都是相同的bean,如果是原型bean,则会获取到不同的bean。 也可以使用@Lookup注解完成该功能。

延迟加载(Lazy initialization mode

默认情况下单例bean会在ApplicationContext实例化过程中完成初始化。在xml中将<bean>lazy-init配置成false,则不会在ApplicationContext实例化过程中进行初始化。也可以在<beans>标签上设置整个配置文件的bean的默认策略如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
复制代码

Srping IoC的初始化主流程

IoC容器初始化流程

下面的代码是初始化Spring容器,加入断点,单步调试,跟踪Spring bean创建的整个流程。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("examples.xml");
复制代码

image.png ClassPathXmlApplicationContext的构造方法如下:

// org.springframework.context.support.ClassPathXmlApplicationContext.java
public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {
    // parent 传入的是 null
   super(parent);
   // 加载解析 xml 配置文件
   setConfigLocations(configLocations);
   if (refresh) {
       // refresh 是创建 bean 的关键方法
      refresh();
   }
}
复制代码

refresh()Spring容器的关键方法,它的代码如下所示:

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // 刷新前对上下文做预处理
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // 获取 BeanFactory,
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // 准备 bean factory
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         // bean factory 的后置处理
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         // 调用 context 中注册为 bean 的 factory 处理器,也就是实现了 BeanFactoryPostProcessor 的 bean
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         // 注册 BeanPostProcessors(bean 的后置处理器)
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         // 初始化 MessageSource 组件,这是 Spring 的国际化特性
         initMessageSource();

         // Initialize event multicaster for this context.
         // 初始化 context 的事件多播器
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         // 子类可以重写这个方法,可以用来初始化一些自定义的 bean
         onRefresh();

         // Check for listener beans and register them.
         // 检查并注册监听器
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         // 实例化所有非懒加载的单例 bean
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         // 刷新 context
         finishRefresh();
      }
      // ......
   }
}
复制代码

BeanFactory创建的流程

refresh(),获取BeanFactory对象调用的obtainFreshBeanFactory(),下面是obtainFreshBeanFactory()的源码:

// org.springframework.context.support.AbstractApplicationContext
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    
   refreshBeanFactory();
   return getBeanFactory();
}
复制代码

refreshBeanFactory()是一个抽象方法,这里调用的是它的子类AbstractRefreshableApplicationContext的实现,如下:

// org.springframework.context.support.AbstractRefreshableApplicationContext.java
@Override
protected final void refreshBeanFactory() throws BeansException {
    // 判断是否已经有了 bean factory
   if (hasBeanFactory()) {
      // 有了 bean factory 先销毁 beans
      destroyBeans();
      // 关闭 bean factory
      closeBeanFactory();
   }
   try {
      // 实例化 DefaultListableBeanFactory
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      // 设置序列化 id
      beanFactory.setSerializationId(getId());
      // 客制化 bean 工厂,设置是否允许 BeanDefinition 的覆盖和循环引用
      customizeBeanFactory(beanFactory);
      // 加载 BeanDefinitions
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}
复制代码

loadBeanDefinitions()这个调用了许多重载方法,一直到调用doLoadBeanDefinitions()方法:

// org.springframework.beans.factory.xml.XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {

   try {
       // 读取 xml 信息
      Document doc = doLoadDocument(inputSource, resource);
      // 注册 BeanDefinition
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
         logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
   }
   // ......
}
复制代码

再关注registerBeanDefinitions()方法

// org.springframework.beans.factory.xml.XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   // 获取已有的 BeanDefinition 数量
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 注册 BeanDefinition
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   // 计算新的 BeanDefinition 数量并返回
   return getRegistry().getBeanDefinitionCount() - countBefore;
}
复制代码

继续跟进registerBeanDefinitions()方法,进入doRegisterBeanDefinitions()方法

// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
protected void doRegisterBeanDefinitions(Element root) {
    // ..... 忽略其它代码,主要关注 parseBeanDefinitions
    parseBeanDefinitions(root, this.delegate);
    // ......
}
复制代码
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) {
               // 从 xml 中解析默认的元素
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}
复制代码
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 先判断了元素的类型
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
       // 对 BEAN 元素的处理
      processBeanDefinition(ele, delegate);
   }
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}
复制代码
// org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         // 注册最终的装饰实例
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Send registration event.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}
复制代码

再跟进registerBeanDefinition()

// org.springframework.beans.factory.support.DefaultListableBeanFactory.java

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

    // ...... 省略其它代码
    // 最后的结果就是将 xml 中的 bean 封装为 BeanDefinition 对象,放入到 map 中
    this.beanDefinitionMap.put(beanName, beanDefinition);
    // ......
}
复制代码

可以看到registerBeanDefinition()的目的就是将XML配置文件中的<bean/>标签里的bean信息封装到一个ConcurrentHashMap容器中,供后续使用。

// org.springframework.beans.factory.support.DefaultListableBeanFactory.java
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
复制代码

Bean的创建流程

回到refresh(),源码的注释上说finishBeanFactoryInitialization(beanFactory)会实例化所有的非懒加载的单例bean,创建bean的流程就由此开始。

// org.springframework.context.support.AbstractApplicationContext.java
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // ...... 省略其它代码
   // Instantiate all remaining (non-lazy-init) singletons.
   beanFactory.preInstantiateSingletons();
}
复制代码

进入preInstantiateSingletons()方法查看:

// org.springframework.beans.factory.support.DefaultListableBeanFactory.java
@Override
public void preInstantiateSingletons() throws BeansException {
   // ......省略部分代码
   // Trigger initialization of all non-lazy singleton beans...
   for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         // 判断是否是 FactoryBean
         if (isFactoryBean(beanName)) {
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {
               final FactoryBean<?> factory = (FactoryBean<?>) bean;
               boolean isEagerInit;
               if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                  isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                              ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
               }
               else {
                  isEagerInit = (factory instanceof SmartFactoryBean &&
                        ((SmartFactoryBean<?>) factory).isEagerInit());
               }
               if (isEagerInit) {
                  getBean(beanName);
               }
            }
         }
         else {
            // 实例化 bean
            getBean(beanName);
         }
      }
   }
   
   // ......省略部分代码

}
复制代码
// org.springframework.beans.factory.support.AbstractBeanFactory.java
@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}
复制代码

再进入doGetBean()方法:

// org.springframework.beans.factory.support.AbstractBeanFactory.java
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
      
    // ...... 省略其它代码
    // Create bean instance.
    if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, () -> {
      try {
          // 实例化 bean
         return createBean(beanName, mbd, args);
      }
      // ...... 省略其它代码
   });
   // ...... 省略其它代码
}
复制代码

继续进入createBean()

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.java
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   // ...... 省略其它代码
   try {
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isTraceEnabled()) {
         logger.trace("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
   // ...... 省略其它代码

}
复制代码

下面是doCreateBean()的源码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      // 从缓存中移除 bean
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      // 创建 bean 实例,如果是 setter 注入方式,这里创建的对象还没有注入属性
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   // ...... 省略一些代码
   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      // 填充 bean 属性
      populateBean(beanName, mbd, instanceWrapper);
      // 初始化 bean
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   // ...... 省略一些代码
}
复制代码

以上就完成了单例bean的创建了。

Bean的生命周期

对于单例bean来说,一个Spring bean从创建到销毁的所有过程都是完全由IoC容器来控制的。Spring中的单例bean的生命周期如下图:

image.png

Spring Bean的循环依赖问题

循环依赖的最简单的情况就是类A依赖于类B,而类B又依赖于类A,因为当实例化A时,要先实例化B,而实例化B又要先实例化A,这样就陷入了一种先有鸡还是先有鸡蛋的问题,这就是Spring Bean的循环依赖。当然实际开发中,开发者不会设计出这样的类来,但是当系统复杂度越来越高,存在很多个Bean时,有可能设计出类A依赖于类B,类B依赖于类C,一直到类Z,类Z又依赖于类A这样的程序来。

Spring Bean使用构造方法注入循环依赖的例子

Spring可以通过setter注入和构造方法注入。下面的代码是一个使用构造方法注入的例子。

public class ExampleBean {

    private AnotherBean anotherBean;

    private int i;

    public ExampleBean(AnotherBean anotherBean, int i) {
        this.anotherBean = anotherBean;
        this.i = i;
    }
}
复制代码
public class AnotherBean {

    private YetAnotherBean yetAnotherBean;
    
    public AnotherBean(YetAnotherBean yetAnotherBean) {
        this.yetAnotherBean = yetAnotherBean;
    }
}
复制代码
public class YetAnotherBean {

    private ExampleBean exampleBean;

    public YetAnotherBean(ExampleBean exampleBean) {
        this.exampleBean = exampleBean;
    }
}
复制代码

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="exampleBean" class="com.linnanc.examples.ExampleBean">
        <constructor-arg>
            <ref bean="anotherExampleBean"/>
        </constructor-arg>
        <constructor-arg type="int" value="1"></constructor-arg>
    </bean>

        <bean id="anotherExampleBean" class="com.linnanc.examples.AnotherBean">
            <constructor-arg>
                <ref bean="yetAnotherBean"/>
            </constructor-arg>
        </bean>

        <bean id="yetAnotherBean" class="com.linnanc.examples.YetAnotherBean">
            <constructor-arg>
                <ref bean="exampleBean"/>
            </constructor-arg>
        </bean>
</beans>
复制代码

测试用例:

public class BeanTest {

    @Test
    public void testConstructor01() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("examples.xml");
        ExampleBean exampleBean = (ExampleBean) applicationContext.getBean("exampleBean");

    }
}
复制代码

运行测试用例获取Bean,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException。如果不用Spring创建实例,写的代码是下面这样:

@Test
public void testConstructor02() {
    // YetAnotherBean 需要的参数是一个 ExampleBean 对象
    YetAnotherBean yetAnotherBean = new YetAnotherBean();
    AnotherBean anotherBean = new AnotherBean(yetAnotherBean);
    ExampleBean exampleBean = new ExampleBean(anotherBean, 1);
}
复制代码

上面的代码在编译器是不会编译通过的。通过构造方法来实例化存在循环引用的对象,这是不被允许的。

Spring Bean使用setter注入循环依赖的例子

public class ExampleBean {

    private AnotherBean anotherBean;

    private int i;

    public void setAnotherBean(AnotherBean anotherBean) {
        this.anotherBean = anotherBean;
    }

    public void setI(int i) {
        this.i = i;
    }
}
复制代码
public class AnotherBean {

    private YetAnotherBean yetAnotherBean;

    public void setYetAnotherBean(YetAnotherBean yetAnotherBean) {
        this.yetAnotherBean = yetAnotherBean;
    }
}
复制代码
public class YetAnotherBean {

    private ExampleBean exampleBean;

    public void setExampleBean(ExampleBean exampleBean) {
        this.exampleBean = exampleBean;
    }
}
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="exampleBean" class="com.linnanc.setter.examples.ExampleBean">
        <property name="anotherBean">
            <ref bean="anotherExampleBean"/>
        </property>
    </bean>

    <bean id="anotherExampleBean" class="com.linnanc.setter.examples.AnotherBean">
        <property name="yetAnotherBean">
            <ref bean="yetAnotherBean"/>
        </property>
    </bean>

    <bean id="yetAnotherBean" class="com.linnanc.setter.examples.YetAnotherBean">
        <property name="exampleBean">
            <ref bean="exampleBean"/>
        </property>
    </bean>
</beans>
复制代码

测试用例:

@Test
public void testSetterInjection01() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("setter.xml");
    com.linnanc.setter.examples.ExampleBean exampleBean = (com.linnanc.setter.examples.ExampleBean) applicationContext.getBean("exampleBean");
}
复制代码

上面的例子是可以成功实例化Bean的。

Spring如何处理循环引用的问题

原型bean不允许循环引用

bean的实例化是通过调用AbstractBeanFactory类中的doGetBean()方法来实现的。这个方法中有对bean循环依赖的处理。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

   final String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   // 紧急检查单例缓存中是否有手动注册的单例
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      if (logger.isTraceEnabled()) {
         if (isSingletonCurrentlyInCreation(beanName)) {
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                  "' that is not fully initialized yet - a consequence of a circular reference");
         }
         else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
         }
      }
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }

   else {
      // Fail if we're already creating this bean instance:
      // We're assumably within a circular reference.
      // 在获取 bean 之前先判断原型 bean 是否正在被创建,如果正在被创建则直接抛出异常,可见原型 bean 是不支持循环引用的
      if (isPrototypeCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(beanName);
      }

     // ...... 省略部分代码
         // Create bean instance.
         if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  return createBean(beanName, mbd, args);
               }
               catch (BeansException ex) {
                  // Explicitly remove instance from singleton cache: It might have been put there
                  // eagerly by the creation process, to allow for circular reference resolution.
                  // Also remove any beans that received a temporary reference to the bean.
                  destroySingleton(beanName);
                  throw ex;
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }
         // 创建原型 bean
         else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
               // 在创建原型 bean 之前进行标记
               beforePrototypeCreation(beanName);
               // 创建原型 bean,以便 isPrototypeCurrentlyInCreation() 能检查到
               prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
               // 创建完成后删除标记
               afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
         }
    // ...... 省略部分代码
 
   return (T) bean;
}
复制代码

可见原型bean的循环引用不论是使用构造方法注入还是setter注入都是不允许的。

单例bean的循环引用

如上面示例,单例bean通过setter注入属性时,是可以循环引用的。理论依据就是Java中的引用传递可以延后设置对象属性。Spring是中通过三级缓存来实现循环引用的。这三级缓存是在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中实现的。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

   /** Cache of singleton objects: bean name to bean instance. */
   // 一级缓存 singletonObjects,存放已经初始化的 bean 对象
   private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
   
   /** Cache of singleton factories: bean name to ObjectFactory. */
   // 三级缓存 singletonFactories,存放可以生成 bean 的工厂 factory
   private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

   /** Cache of early singleton objects: bean name to bean instance. */
   // 二级缓存 earlySingletonObjects,存放早期提前暴露出来的 bean 对象,即完成实例化,但未注入属性的 bean
   private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
   
}
复制代码
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   // ...... 省略部分代码
   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
   // 判断是否需要提前暴露
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
         
   if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      // 需要提前暴露,先将 bean 放到三级缓存 singletonFactories,并从二级缓存 earlySingleton 中移除
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      populateBean(beanName, mbd, instanceWrapper);
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      }
      else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }

   if (earlySingletonExposure) {
      // 对于提前暴露的 bean,这里调用 getSingleton 传入了 false,不会将 bean 添加到二级缓存
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   // ...... 省略部分代码
   return exposedObject;
}
复制代码

整个流程就是ClassA在创建时依赖ClassB,于是将自己添加到三级缓存singletonFactories中,再去实例化ClassBClassB实例化时又依赖ClassA,此时ClassB调用getSingleton()去缓存中查找ClassA,查找到了则将ClassA从三级缓存中删除,并添加到二级缓存earlySingletonObjects中,再完成ClassB的属性设置并将自己放入到一级缓存,接下来再回来初始化ClassAClassA从一级缓存中查找到ClassB,然后完成依赖注入,再将自己从二级缓存删除,添加到一级缓存。

猜你喜欢

转载自juejin.im/post/7068942879078481950