我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
在前一篇文章Spring 快速开始中,使用了下面的代码来实例化Bean
对象,使用了一个ApplicationContext
类,并没有使用new
关键字。实例化Bean
对象的过程交给了Spring
,而无需开发者显式的使用new
等方法去实现,这就是Spring
的IoC
设计思想。
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
中,配置元数据支持XML
,Java
注解,Java
代码实现。前面示例就是使用的XML
来描述配置元数据。
IoC
容器
ApplicationContext
在前面示例中,构造了一个ApplicationContext
对象,然后通过调用getBean()
方法获取到HelloWorld
对象。ApplicationContext
是什么呢?单从名称上看ApplicationContext
是应用程序上下文。
可以看它继承了BeanFactory
,看到BeanFactory
便能想到熟悉的抽象工厂模式(Abstract Factory Mode
),Spring IoC
容器也是使用了抽象工厂模式来创建Bean
对象。ApplicationContext
就是Spring IoC
容器的代表实现,它扩展了BeanFactory
,并具备BeanFactory
的所有功能,且在其基础上增加了用于国际化的消息资源处理,应用层特定的上下文等。 ApplicationContext
的子接口实现类如下图:
子类FileSystemXmlApplicationContext
和ClassPathXmlApplicationContext
都是从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.xml
,classes
也就是ClassPath
。在docs.xml
的bean
标签里面有两个属性id
和class
。在BeanFactory
中有如下两个getBean()
方法:
public interface BeanFactory {
// ......
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
// ......
}
复制代码
Object getBean(String name)
传入的参数name
是id
的值,id
是用来唯一标识bean
的属性,<T> T getBean(Class<T> requiredType)
传入的requiredType
是class
的值,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");
复制代码
Bean
在Spring
官方文档Bean Overview一节中,Bean
定义有Name
,Class
,Scope
,Constructor arguments
,Properties
,Autowiring mode
,Lazy initialization mode
,Initialization method
,Destruction method
这些属性。
Name
在前面使用xml
配置文件中,有用到id
属性,id
属性可以唯一的标识一个bean
。name
属性和id
属性一样,都可以唯一的标识bean
。如果没有显式指定id
或name
,容器会给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
对象。
使用实例工厂方法实例化
使用实例工厂方法实例化与通过静态工厂方法实例化类似,要使用这种方式实例化对象,bean
的class
属性要保存为空,并在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
Scope
是bean
的作用范围。Spring
框架中支持六种作用范围,有四种作用范围是当使用基于Web
的ApplicationContext
才有效的,也可以自定义作用范围。
作用域 | 描述 |
---|---|
singleton |
(默认)每一个Spring IoC 容器都拥有唯一的实例对象 |
prototype |
一个bean 定义可以创建任意多个实例对象 |
request |
将单个bean 定义范围限定为单个HTTP 请求的生命周期。也就是说,每个HTTP 请求都有自己的bean 实例,它是在单个bean 定义的后面创建的。只有基于Web 的ApplicationContext 才可用 |
session |
将单个bean 定义范围限定为HTTP session 的生命周期。只有基于Web 的ApplicationContext 才可用 |
application |
将单个bean 定义范围限定为ServletContext 的生命周期。只有基于Web 的ApplicationContext 的才可用。 |
websocket |
将单个bean 定义范围限定为WebSocket 的生命周期,只有基于Web 的ApplicationContext 才可用 |
单例作用域
单例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-processor
(bean
后置处理器)来处理。
依赖注入
通常一个应用中都不会只有一个对象,例如前面的TransferServiceImpl
要依赖AccountDao
才能工作,前面的代码中使用AccountDao
是手动调用getBean()
从Spring
容器中获取的。通过DI
(依赖注入)可以让对象只通过构造参数,工厂方法的参数或者配置属性来获取依赖对象。容器会在创建bean
的时候自动的注入依赖,无需再手动的调用getBean()
获取,这个过程将开发者自己控制实例化bean
交给了Spring
容器,所以也被称为控制反转(IoC
)。
依赖注入的方式
bean
的依赖注入方式有两种:基于构造方法和基于setter
方法。
基于构造方法的依赖注入(Constructor arguments
)
基于构造方法依赖注入使用的是Bean
的Constructor 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
方法依赖注入使用的是Bean
的Properties
属性,要在<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
方法?
由于可以混合使用基于构造方法和基于
setter
的DI
,对于必要的依赖使用构造方法,对于可选依赖使用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
集合类型List
,Set
,Map
,Properties
配置了依赖注入。
例如:
<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
设置为false
将bean
从自动装配中移除。
方法注入
当一个单例bean
依赖于原型bean
时,由于单例bean
在Spring
启动时便已实例化完成,接下来再获取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 transferService
对ApplicationContext
可见,从而通过调用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");
复制代码
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
的生命周期如下图:
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
中,再去实例化ClassB
,ClassB
实例化时又依赖ClassA
,此时ClassB
调用getSingleton()
去缓存中查找ClassA
,查找到了则将ClassA
从三级缓存中删除,并添加到二级缓存earlySingletonObjects
中,再完成ClassB
的属性设置并将自己放入到一级缓存,接下来再回来初始化ClassA
,ClassA
从一级缓存中查找到ClassB
,然后完成依赖注入,再将自己从二级缓存删除,添加到一级缓存。