由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp...
在Spring
的使用中常常会有bean
循环依赖的发生,貌似给人感觉就是这个概念是Spring
特有的,但实际上在软件工程中的反模式(anti-pattern
)中早已将臭名昭著的循循环依赖原则(ADP
)登记在册了,只不过在Spring
依赖注入的特性加持下循环依赖变得显而易见了,这也是大部分IoC
框架所能遇见的问题。
循环依赖的发生
简单来说,循环依赖即是指模块间的属性存在相互依赖的关系。典型地,在Spring
中我们可以轻松地找到以下触发到循环依赖的场景:
-
构造器自动装配发生循环依赖
java@Service public class ServiceOne { private ServiceTwo serviceTwo; @Autowired public ServiceOne(ServiceTwo serviceTwo){ this.serviceTwo = serviceTwo; } } @Service public class ServiceTwo { private ServiceOne serviceOne; @Autowired public ServiceTwo(ServiceOne serviceOne){ this.serviceOne = serviceOne; } }
-
工厂方法自动装配发生循环依赖
java@Bean public BeanA beanA(BeanB beanB) { return new BeanA(); } @Bean public BeanB BeanB(BeanA beanA) { return new BeanB(); } public class BeanA { } public class BeanB { }
-
Setter
方法自动装配发生循环依赖java@Service public class ServiceOne { private ServiceTwo serviceTwo; @Autowired public void setServiceTwo(ServiceTwo serviceTwo){ this.serviceTwo = serviceTwo; } } @Service public class ServiceTwo { private ServiceOne serviceOne; @Autowired public void setServiceOne(ServiceOne serviceOne){ this.serviceOne = serviceOne; } }
-
字段自动装配发生循环依赖
java@Service public class ServiceOne { @Autowired private ServiceTwo serviceTwo; } @Service public class ServiceTwo { @Autowired private ServiceOne serviceOne; }
其中,对于上述某些场景在发生循环依赖时可能会被Spring
顺利解决而避免异常的发生;对于另一些场景发生循环依赖时则可能无法被Spring
解决而抛出循环依赖异常。为了避免在使用Spring
的过程中由于使用不当触发到循环依赖异常,下面我们从Spring
对循环依赖的的解决方案入手在原理上认识到如何规避循环依赖异常的发生。
循环依赖的解决
在通过AbstractBeanFactory#doGetBean
方法执行bean
的实例化的时候,Spring
会在其中夹杂着对循环依赖的处理,以此来尽可能地避免在实例化过程中循环依赖异常的发生。那下面我们将从AbstractBeanFactory#doGetBean
方法开始对Spring
循环依赖的解决思路进行分析。
为了更清晰的阐述,这里将bean
的创建分为单例模式和多例模式2
种:
- 单例模式(
Singleton
)的bean
创建。 - 多例模式(
Prototype
)的bean
创建。
由于单例模式和多例模式下对循环依赖的处理方式基本涵盖了所有场景,我们完全可以结合单例模式和多例模式下对循环依赖的处理方式来完成对其他作用域的处理分析,因此在这里就不对
Spring
其他类型的作用域展示分析了。
单例模式的bean
创建
在单例模式下,doGetBean
方法首先会逐个从不同的缓存级别中获取bean
实例,如果其中某个层级不为空则将其取出并进一步处理后返回;而如果各个层级都不存在则表示当前bean
尚未被实例化,因此对该bean
执行实例化操作。下面,我们看到具体的代码步骤:
注意,在此章节贴出的代码并非完整代码,而是经过笔者简化后仅作用于实例化单例作用域
bean
的代码。
java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws ... {
String beanName = ...;
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName, true);
if (sharedInstance != null && args == null) {
// ...
beanInstance = getObjectForBeanInstance(sharedInstance, ...);
}
else {
// ...
RootBeanDefinition mbd = ...;
// ...
// Create bean instance.
sharedInstance = getSingleton(beanName, () -> {
// 假设没有异常
return createBean(beanName, mbd, args);
});
beanInstance = getObjectForBeanInstance(sharedInstance, ...);
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
在doGetBean
方法中,首先会使用getSingleton(String, boolean)
方法在缓存中查找出对应的bean
实例,在查找失败的情况下再使用getSingleton(String, ObjectFactory)
方法创建对应的bean
实例。虽然这么一看其执行流程与常规的缓存策略大致相同,但实际上它会在常规缓存策略的基础上添加了多级缓存策略和构建重复性验证来避免循环依赖的发生。
在进行具体的执行流程之前,我们先来对Spring
在单例bean
的实例化过程中涉及到的多级缓存策略提前进行梳理。具体地,bean
在实例化过程中为保证单例作用域的特性、循环依赖的处理和一些设计理念的考虑一共使用到了3
个级别的缓存,即:
缓存 | 等级 | 说明 |
---|---|---|
singletonObjects |
三级缓存 | 缓存已完成的bean 实例,已执行完成bean 的初始化。 |
earlySingletonObjects |
二级缓存 | 缓存构建初期的bean 实例,尚未执行bean 实例的初始化。 |
singletonFactories |
一级缓存 | 缓存构建bean 的工厂实例,尚未执行bean 实例的创建。 |
根据上述对每个级别缓存的阐述,基本可以分析出:
- 在三级缓存
singletonObjects
的作用下保证了bean
单例作用域的特性; - 在二级缓存
earlySingletonObjects
的作用下完成了循环依赖的解决,这其实与常规的循环依赖解决方案大致相同,即通过缓存将尚未执行初始化的对象提前暴露出去; - 在一级缓存
singletonFactories
的作用下可以延迟二级缓存中earlySingletonObject
对象的生成(只有在循环依赖真正发生时才特意生成出earlySingletonObject
对象),毕竟循环依赖在bean
的引用关系中并不一定会发生(Spring
也不推荐在bean
的引用关系中发生循环依赖)。
当然,对各级缓存作用的分析仅是基于它们表面的代码行为,而对其在Spring
中真正发挥到的作用还需要进一步阅读doGetBean
方法的执行逻辑。那下面我们就开始展开对doGetBean
方法的阅读理解与逻辑分析。
在doGetBean
方法的执行流程中,Spring
首先会通过getSingleton(String, boolean)
方法从bean
的多级缓存中查询缓存实例:
java
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 忽略并发情况下的获取: Consistent creation of early reference within full singleton lock
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
其中,getSingleton(String, boolean)
方法正如上述所说会逐级查询缓存实例,若查询成功则在经过相应的处理后返回。具体地,getSingleton
会通过第2
个参数allowEarlyReference
来控制是否执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建。下面,我们将根据allowEarlyReference
参数不同的值总结出不同的执行流程:
-
当
allowEarlyReference
参数为true
,允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建:- 从三级缓存中查询已执行初始化完成的
bean
实例singletonObject
,如果查询存在直接返回(保证了bean
单例作用域的特性),否则执行第2
步。 - 从二级缓存中查询构建初期尚未执行初始化的
bean
实例earlySingletonObject
,如果查询存在直接返回(证实了bean
发生过循环依赖),否则执行第3
步。 - 从一级缓存中查询是否存在构建
bean
的工厂实例singletonFactory
,如果不存在直接返回null
(证实了bean
尚未被创建),否则:- 通过
singletonFactory
实例创建出earlySingletonObject
实例。 - 将
earlySingletonObject
实例添加到二级缓存中。 - 将
singletonFactory
实例从一级缓存中移除。 - 将
earlySingletonObject
实例返回。
- 通过
即,
getSingleton(String, true)
方法会分别从三级缓存singletonObjects
、二级缓存earlySingletonObjects
到一级缓存singletonFactorys
中查询对应的缓存实例,如果在三级缓存、二级缓存中查询成功则直接返回;如果在一级缓存中查询成功则根据工厂实例创建出earlySingletonObject
实例添加到二级缓存并返回该实例;如果都没有查询成功则返回null
表示尚未开始bean
实例的创建。text+ | | v +---------+----------+ | | success | singletonObjects +------------>+ | | | +---------+----------+ | | | | | | failure | | | v | +-----------+------------+ | | | success | +---------->+ earlySingletonObjects +---------->+ | | | | | +-----------+------------+ | | | | | | | success | | failure | | | | | v | | +----------+-----------+ | | | | | +------------+ singletonFactories | | | | | +----------+-----------+ | | | | | | failure | | | v v null singletonObject/earlySingletonObject
- 从三级缓存中查询已执行初始化完成的
-
当
allowEarlyReference
参数为false
,不允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建:- 从三级缓存中查询已执行初始化完成的
bean
实例singletonObject
,如果查询存在直接返回(保证了bean
单例作用域的特性),否则执行第2
步。 - 从二级缓存中查询构建初期尚未执行初始化的
bean
实例earlySingletonObject
,如果查询存在直接返回(证实了bean
发生循环依赖),否则直接返回null
。
即,
getSingleton(String, false)
方法会分别从三级缓存singletonObjects
和二级缓存earlySingletonObjects
中查询对应的缓存实例,如果在三级缓存或者二级缓存中查询成功则直接返回,否则返回null
。text+ | | v +---------+----------+ | | success | singletonObjects +------------>+ | | | +---------+----------+ | | | | | | failure | | | v | +-----------+------------+ | | | success | | earlySingletonObjects +---------->+ | | | +-----------+------------+ | | | | | | failure | | | v v null singletonObject/earlySingletonObject
- 从三级缓存中查询已执行初始化完成的
在代码中我们可以看到doGetBean
方法执行的是getSingleton(String, true)
方法,即会分别从三级缓存singletonObjects
、二级缓存earlySingletonObjects
到一级缓存singletonFactorys
中查询对应的缓存实例。为了能完整地描述循环依赖的整个流程,这里就暂且假设bean
尚未被创建过。因此,Spring
在执行getSingleton(String, true)
方法时返回null
,随即进入到getSingleton(String, ObjectFactory)
方法执行bean
实例的创建,具体如下所示:
java
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ...
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
// 假设无异常
singletonObject = singletonFactory.getObject();
newSingleton = true;
} finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
如同常规单例模式下的实例创建流程,在getSingleton(String, ObjectFactory)
方法中也会优先从三级缓存singletonObjects
中获取实例,在获取失败后才会执行singletonObject
对象的创建。而在singletonObject
的创建流程中,除了会完成singletonObject
实例的创建,还添加了实例创建的前置处理beforeSingletonCreation
与后置处理afterSingletonCreation
,具体代码如下所示:
java
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* Callback before singleton creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
protected void beforeSingletonCreation(String beanName) {
if (... && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
/**
* Callback after singleton creation.
* <p>The default implementation marks the singleton as not in creation anymore.
* @param beanName the name of the singleton that has been created
* @see #isSingletonCurrentlyInCreation
*/
protected void afterSingletonCreation(String beanName) {
if (... && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("...");
}
}
在前置处理beforeSingletonCreation
(创建bean
实例前)中,Spring
会尝试将当前beanName
加入到singletonsCurrentlyInCreation
中,若加入失败则抛出异常BeanCurrentlyInCreationException
;而后置处理afterSingletonCreation
(创建bean
实例后)中,Spring
会尝试将当前beanName
从singletonsCurrentlyInCreation
中移除,若移除失败则抛出异常IllegalStateException
。
实际上,
Spring
就是通过这种前置处理和后置处理来判断bean
在实例化过程中是否发生了无法解决的循环依赖,进而在请求时抛出异常提前阻止程序进入无限循环。
典型地,当Spring
在构造器自动装配时发生了循环依赖即会在前置处理中触发BeanCurrentlyInCreationException
异常的发生,具体流程如下图所示:
text
+
|
|
v
+-------------+-------------+
throw ex | | circular references
<------------+ beforeSingletonCreation +<----------------------+
| | |
+-------------+-------------+ |
| |
| |
| |
v |
+--------------+---------------+ +----------+-----------+
| | | |
| singletonFactory#getObject +--------->+ createBeanInstance |
| | | |
+--------------+---------------+ +----------+-----------+
| |
| | To expose earlySingletonObject
| |
v v
+------------+-------------+
| |
| afterSingletonCreation |
| |
+--------------------------+
回归到正常流程上,如果在前置处理beforeSingletonCreation
没有发现循环依赖,那么Spring
就会进入到createBean
方法执行bean
实例的创建。
java
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
//...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 1. 执行`raw bean`实例的创建
BeanWrapper instanceWrapper = ...createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ...
// 2. 通过`raw bean`实例构建工厂实例并将其添加到一级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Object exposedObject = bean;
// 3. 执行`raw bean`实例的属性填充
populateBean(beanName, mbd, instanceWrapper);
// 4. 执行`raw bean`实例的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 5. 归正最终返回的`bean`实例
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (...) {
...
}
}
// ...
return exposedObject;
}
在createBean
方法中不但会执行基本的bean
实例化流程,而且还会额外添加解决循环依赖的代码块,具体执行流程如下所示:
- 执行
raw bean
实例的创建 - 通过
raw bean
实例构建工厂实例并将其添加到一级缓存 - 执行
raw bean
实例的属性填充 - 执行
raw bean
实例的初始化 - 归正最终返回的
bean
实例
其中,步骤1
、3
和4
都是bean
实例化流程的基本步骤,这在上文《源码分析:Bean
的创建》章节中已有所提及,这里就不在赘述了。而步骤2
和步骤5
则主要用于解决bean
的循环依赖,下面我们来详细看看这2
个步骤:
根据上述流程,Spring
在执行完bean
基本对象的创建后就会执行步骤2
(在属性填充和实例初始化前),即通过raw bean
实例构造出工厂实例并将其添加到一级缓存:
java
// 视`() -> getEarlyBeanReference(beanName, mbd, bean)`为工厂实例
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
通过这种方式,如果Spring
在属性填充阶段发生了循环依赖再次向doGetBean
方法请求当前bean
时就会触发到getSingleton(String, true)
方法的执行(此时在一级缓存中会存在当前bean
的工厂实例缓存)。在getSingleton(String, true)
方法中会一路探查到一级缓存将当前存储的工厂实例取出执行(即执行getEarlyBeanReference
方法),具体执行逻辑如下所示:
java
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* @param beanName the name of the bean (for error handling purposes)
* @param mbd the merged bean definition for the bean
* @param bean the raw bean instance
* @return the object to expose as bean reference
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
//...假设符合条件
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* <p>This callback gives post-processors a chance to expose a wrapper
* early - that is, before the target bean instance is fully initialized.
* The exposed object should be equivalent to the what
* {@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}
* would expose otherwise. Note that the object returned by this method will
* be used as bean reference unless the post-processor returns a different
* wrapper from said post-process callbacks. In other words: Those post-process
* callbacks may either eventually expose the same reference or alternatively
* return the raw bean instance from those subsequent callbacks (if the wrapper
* for the affected bean has been built for a call to this method already,
* it will be exposes as final bean reference by default).
* <p>The default implementation returns the given {@code bean} as-is.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @return the object to expose as bean reference
* (typically with the passed-in bean instance as default)
*/
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
}
在getEarlyBeanReference
方法中会遍历所有的SmartInstantiationAwareBeanPostProcessor
对raw bean
进行修饰处理,默认情况下SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
不会做任何处理直接原样返回bean
,而如果Spring
使用了AOP
特性则会通过其实现类中的AbstractAutoProxyCreator#getEarlyBeanReference
方法对bean
包装一层代理,具体如下所示:
java
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
* that wraps each eligible bean with an AOP proxy, delegating to specified interceptors
* before invoking the bean itself.
* ...
*/
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, ... {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 特别需要关注这里,在对bean包装前会将bean存入缓存中
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// ...
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// ...
return bean;
}
}
也就是说,在默认情况下如果没有使用Spring AOP
,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
会原样返回bean
实例;而如果使用了Spring AOP
,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
则会包装一层代理层再返回。而不管在哪种情况下执行,getSingleton(String, true)
方法在通过getEarlyBeanReference
方法获得earlySingletonObject
实例后就将它添加到二级缓存中,并最终将earlySingletonObject
实例返回,完成对早期bean
的提前暴露,从而终止对bean
的再次创建、属性填充和实例初始化,避免程序在循环依赖中不断地对bean
进行实例化的困境。Spring
就是通过这种方式来解决bean
的循环依赖的,具体流程如下图所示:
text
+
| get A
v
+-------+--------+
+--------+ getSingleton +<-----------------------------------+
| +-------+--------+ |
| | |
| v |
| +----------+-----------+ +--------------+ +-------+ |
| | createBeanInstance +-->+ populateBean +-->+ |-| +--->
| +----------------------+ +------+-------+ +-------+ |
| | get B | get A
v v |
+-----------+-----------+ +-------+--------+ |
| getSingletonFactory | | getSingleton | |
+-----------+-----------+ +-------+--------+ |
| | |
v v |
+------------+------------+ +----------+-----------+ +------+-------+ +-------+
| getEarlyBeanReference | | createBeanInstance +-->+ populateBean +-->+ |-| +--->
+------------+------------+ +----------------------+ +------+-------+ +-------+
| ^
| |
+--------------------------------------------------------------+
一般来说,
getEarlyBeanReference
方法返回的对象与postProcessBeforeInitialization
/postProcessAfterInitialization
所返回的对象是相等的。不过,Spring
并没有特意对此作强制要求,如果我们在postProcessBeforeInitialization
/postProcessAfterInitialization
中返回与之前不一样的引用就会造成它们之间的等式不成立。
再结合上文bean
创建流程中前置处理beforeSingletonCreation
方法与后置处理afterSingletonCreation
方法的判断,不难得出Spring
就是通过这种将早期bean
提前暴露到缓存的方式来避免bean
依赖的创建再次进入到前置处理beforeSingletonCreation
方法中造成异常的抛出,具体可参考以下流程图:
text
|
|
v
+--------+-------+
| | circular references
+--------------+ getSingleton +<--------------------------------------------------+
| | | |
| +--------+-------+ |
| | |
| create if not exists |
| | |
| +------------------------------------+ |
| | | | |
| | v | |
| | +-------------+-------------+ | |
| | | | | |
| | | beforeSingletonCreation | | |
| | | | | |
| | +-------------+-------------+ | |
| | | | |
| | | | |
| | | | |
| | v | |
| | +--------------+---------------+ | +----------------------+ |
| | | | | | | |
| | | singletonFactory#getObject +--------->+ createBeanInstance | |
| | | | | | | |
| | +--------------+---------------+ | +----------+-----------+ |
| | | | | |
| | | | To expose earlySingletonObject |
| | | | | |
| | v | v |
| | +------------+-------------+ | +----------+------------+ |
| | | | | | | |
| | | afterSingletonCreation | | | addSingletonFactory | |
| | | | | | | |
| | +--------------------------+ | +----------+------------+ |
| | | | |
| | | | |
| +------------------------------------+ | |
| v |
| +-------+---------+ |
| | | |
+---------------------------------------------------->+ populateBean +-----------+
get from cache if exists | |
+-------+---------+
|
|continue
|
v
简单来说,Spring
就是通过将早期bean
实例earlySingletonObject
提前暴露出去的方式来解决bean
的循环依赖。这样一看感觉Spring
已经完成bean
的循环依赖的处理了,但实际上这里仅仅考虑了没有代理层(特指AbstractAutoProxyCreator
,包含Spring AOP
的处理)情况下的循环依赖,而对于循环依赖发生在需经过代理层处理(特指AbstractAutoProxyCreator
,包含Spring AOP
的处理)的bean
实例时则要加上步骤5
的配合了。
其中,createBean
方法步骤5
的执行流程如下所示:
- 通过
getSingleton(String, false)
方法获取缓存earlySingletonReference
,如果存在则继续执行第2
步,否则结束执行。 - 通过
exposedObject
属性判断bean
实例初始化前后引用是否发生过变化,如果没发生过变化则继续执行第3
步,否则结束执行。 - 将
earlySingletonReference
赋值给exposedObject
属性替换最终暴露的bean
实例。
需要注意,
getSingleton(String, false)
方法是不允许执行一级缓存singletonFactories
的查询与二级缓存earlySingletonObject
的创建。
可能对于上述步骤5
的执行流程看上去会有点迷惑,下面将结合上下文对它进一步解读:
- 对于
getSingleton(String, false)
方法,如果earlySingletonReference
为非空则表示发生过循环依赖,否则未发生过。 - 对于
initializeBean
实例初始化,如果其中初始化前置处理或者后置处理发生了类似于添加代理层的操作就会导致引用发生变化,否则不发生。
对于第
2
点中提及到在实例初始化的前置处理或者后置处理中如果将传入的实例对象变更为另一种对象也会导致引用发生变化,不过由于这种操作并非常规操作(Spring
也不推荐),在这里就不往这个方向分析了。
根据对Spring AOP
的了解,在使用Spring AOP
时会使用动态代理为bean
添加一层代理层。按照这种前提显然使用了Spring AOP
的情况是不会触发到步骤5
的,但对于没有使用Spring AOP
的场景就更让人觉得匪夷所思了,因为在这种情况下无论是通过getEarlyBeanReference
方法提前暴露的对象还是在经过实例初始化后的对象都是不变的(它们都是原始的bean
引用),这样就更没有替换的必要了。在笔者一筹莫展之际,重新阅读了一遍用于实现Spring AOP
的AbstractAutoProxyCreator
类,并且在结合用于提前暴露早期bean
的getEarlyBeanReference
方法和用于实例初始化的postProcessAfterInitialization
方法后找到了答案,下面我们先来看看这2
个方法:
java
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, ... {
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 在raw bean进行Spring AOP前将它存入earlyProxyReferences缓存中
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 从earlyProxyReferences缓存中取出raw bean进行判断
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}
看到这里答案应该呼之欲出了,我们拿到这两个方法代入到doCreateBean
方法中就清晰明了了,即:
- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 将第
2
步中传入的raw bean
实例添加到earlyProxyReferences
缓存。 - 将第
2
步中传入的raw bean
实例包装代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 将第
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
AbstractAutoProxyCreator
后置处理器(包含Spring AOP
的处理)的postProcessAfterInitialization
方法中判断传入的bean
实例与第3
步循环依赖中触发earlyProxyReferences
缓存的bean
实例相等(都为raw bean
实例),跳过执行对传入bean
实例(raw bean
实例)的代理封装(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现没有发生过变化(因为在4
步中跳过了bean
实例的代理封装),继续往下执行。 - 将
exposedObject
属性设置为earlySingletonReference
,完成将提前暴露的代理对象归正给当前返回的exposedObject
属性,保证了循环依赖提前暴露出去的对象与初始化后返回的对象一致。
- 通过
简单来说,Spring
在循环依赖发生时将封装了代理的raw bean
提前暴露出去,然后在属性填充和初始化(不包含类似Spring AOP
等一系列会让bean
引用发生变化的处理)阶段对raw bean
内的属性等进行的赋值/修改操作会同时作用于传入bean
引用与提前暴露出去包含在代理中的bean
引用(两者为同一bean
引用),而在初始化阶段中对后置处理器AbstractAutoProxyCreator
(包含Spring AOP
的处理)的执行会对发生过bean
提前暴露的实例不做代理封装并在初始化结束后将提前暴露的代理实例赋值给最终返回的bean
实例(替换)。通过这种方式,Spring
在最大程度上保证循环依赖提前暴露的出去的对象与在初始化后返回的对象一致,这也呼应了上文提到的:"一般来说,getEarlyBeanReference
方法返回的对象与postProcessBeforeInitialization
/postProcessAfterInitialization
所返回的对象是相等的"。
不难得出,如果bean
仅通过AbstractAutoProxyCreator
(包含Spring AOP
的处理)后置处理器来生成代理层(没有覆盖非protected
的方法),无论在初始化阶段中后置处理器按照哪个顺序执行,最终都能保证提前暴露的bean
引用与初始化执行后的bean
引用一致(通过AbstractAutoProxyCreator
来添加代理层可以确保getEarlyBeanReference
方法和postProcessAfterInitialization
方法都有添加代理层的逻辑,使得最终循环依赖提前暴露的bean
引用与初始化执行后的bean
引用一致)。
没进行过
Spring AOP
的bean
实例也是会执行步骤5
(归正最终返回的bean
实例)的,但是由于提前暴露的bean
引用与初始化后的bean
引用并没有发生过变化,因此在这种情况下步骤5
是冗余处理。
但如果我们在bean
实例化的过程中添加自定义的SmartInstantiationAwareBeanPostProcessor
来生成代理层,那最终生成的bean
实例很有可能会存在一定的问题。而这其中的问题会根据自定义的SmartInstantiationAwareBeanPostProcessor
的执行顺序(在AbstractAutoProxyCreator
前后)有所不同,下面我们将根据自定义的SmartInstantiationAwareBeanPostProcessor
的执行顺序分点阐述。
首先,假设下面SmartInstantiationAwareBeanPostProcessor
类中getEarlyBeanReference
方法和postProcessAfterInitialization
方法生成代理层的逻辑是相同的,因为在不相同的情况下提前暴露的对象与初始化后所生成的对象必然不一致(无任何探讨的意义)。同时这里也得出一个结论:在实现SmartInstantiationAwareBeanPostProcessor
类时必须保证getEarlyBeanReference
方法和postProcessAfterInitialization
方法中的逻辑是相同的,以此来达到提前暴露的对象与初始化后所生成的对象是一致的。
另外,如果我们使用更顶级的
BeanPostProcessor
类来生成代理层,那么最终生成的bean
实例也极有可能是有问题的。根据上文分析,在发生循环依赖时使用getEarlyBeanReference
方法提前暴露bean
实例(含代理层)是通过SmartInstantiationAwareBeanPostProcessor
类来实现的,如果我们使用BeanPostProcessor
类来实现的话就会导致在getEarlyBeanReference
方法中暴露的对象没有经过BeanPostProcessor
来生成代理层,而在初始化后生成的对象则是经过BeanPostProcessor
来生成代理层,这显然会发生bean
实例不一致的问题。
-
当生成代理层的自定义
SmartInstantiationAwareBeanPostProcessor
在AbstractAutoProxyCreator
之前:text+ | v +--------------------+-----------------------+ | | | SmartInstantiationAwareBeanPostProcessor | | | +--------------------+-----------------------+ | v +-------------+--------------+ | | | AbstractAutoProxyCreator | | | +-------------+--------------+ | v
在这种情况下,
doCreateBean
方法的执行流程就演变为下面这样了:- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性(发生了循环依赖)。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 在自定义
SmartInstantiationAwareBeanPostProcessor
中为传入bean
实例封装一层代理层(方法返回,进入AbstractAutoProxyCreator
继续执行)。 - 在
AbstractAutoProxyCreator
中将传入bean
实例添加到earlyProxyReferences
缓存中。 - 在
AbstractAutoProxyCreator
中将传入bean
实例封装一层代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 在自定义
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
SmartInstantiationAwareBeanPostProcessor
的postProcessAfterInitialization
方法中为传入bean
实例封装一层代理层(方法返回)。 - 在
AbstractAutoProxyCreator
的postProcessAfterInitialization
方法中判断传入的bean
实例与从第2
步AbstractAutoProxyCreator#earlyProxyReferences
缓存中的bean
实例并不想等(传入的是在SmartInstantiationAwareBeanPostProcessor#postProcessAfterInitialization
中添加了代理层的bean
实例,而AbstractAutoProxyCreator#earlyProxyReferences
缓存的是第3
步中SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
添加了代理层的bean
实例,在没有重写等式判断方法和添加缓存的前提下是不相等的),所以接着为传入bean
实例的代理封装(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现发生过变化,结束执行(不替换)。
- 通过
在上述流程中我们可以看到虽然循环依赖提前暴露的对象引用与初始化后获得的对象引用是不相等的,但是它们所实现的代理层和功能点都是相同的。这在常规的场景下一般是不会产生什么大的问题的,因为在外层代理层功能相同的情况下表明了最终所经过的额外逻辑是相同的,而最终委托给被代理对象时所执行的对象也是同一单例作用域下的实例。
- 执行
-
当生成代理层的自定义
SmartInstantiationAwareBeanPostProcessor
在AbstractAutoProxyCreator
之后:text+ | v +-------------+--------------+ | | | AbstractAutoProxyCreator | | | +-------------+--------------+ | v +--------------------+-----------------------+ | | | SmartInstantiationAwareBeanPostProcessor | | | +--------------------+-----------------------+ | v
在这种情况下,
doCreateBean
方法的执行流程就演变为下面这样了:- 执行
createBeanInstance
方法创建raw bean
实例。 - 将第
1
步中的raw bean
实例传入addSingletonFactory
方法创建出其bean
的工厂实例并将其添加到一级缓存中。 - 执行
populateBean
方法给第1
步中的raw bean
实例填充属性(发生了循环依赖)。- 在执行期间发生了循环依赖再次执行
getSingleton(String, true)
方法查询出在第2
步中缓存的工厂实例,拿出该工厂实例触发getEarlyBeanReference
方法的执行。- 在
AbstractAutoProxyCreator
中将传入bean
实例添加到earlyProxyReferences
缓存中。 - 在
AbstractAutoProxyCreator
中将传入bean
实例封装一层代理层(方法返回,进入SmartInstantiationAwareBeanPostProcessor
继续执行)。 - 在自定义
SmartInstantiationAwareBeanPostProcessor
中为传入bean
实例封装一层代理层(方法返回,结束getSingleton(String, true)
方法的执行(查询成功,结束循环依赖))。
- 在
- 在执行期间发生了循环依赖再次执行
- 执行
initializeBean
方法给第1
步中的raw bean
实例初始化。- 在
AbstractAutoProxyCreator
的postProcessAfterInitialization
方法中判断传入的bean
实例与从第2
步中AbstractAutoProxyCreator#earlyProxyReferences
缓存中的bean
实例相等,跳过执行对传入bean
实例的代理封装(方法返回)。 - 在
SmartInstantiationAwareBeanPostProcessor
的postProcessAfterInitialization
方法中为传入bean
实例封装一层代理层(方法返回)。
- 在
- 归正最终返回的
bean
实例- 通过
getSingleton(beanName, false)
方法获取到earlySingletonReference
实例不为空(发生过循环依赖),继续往下执行。 - 通过比较
exposedObject
实例发现发生过变化,结束执行(不替换)。
- 通过
在上述流程中我们可以看到循环依赖提前暴露的对象引用与初始化后获得的对象不但引用是不相等的,而且在功能上也是有所差异,即对循环依赖提前暴露的对象是添加了
AbstractAutoProxyCreator
和SmartInstantiationAwareBeanPostProcessor
两个代理层的,而在初始化后所得到的对象只添加了SmartInstantiationAwareBeanPostProcessor
的代理层,这种导致功能缺失的差异显然是致命的。因此,我们在开发过程中应该尽可能的保证这种执行顺序的发生。与此同时,为了避免这种情况的发生
Spring
也通过ProxyProcessorSupport
将AbstractAutoProxyCreator
的执行顺序设置到了最后。javapublic class ProxyProcessorSupport extends ... implements Ordered, ... { /** * This should run after all other processors, so that it can just add * an advisor to existing proxies rather than double-proxy. */ private int order = Ordered.LOWEST_PRECEDENCE; @Override public int getOrder() { return this.order; } }
在开发过程中,我们应该尽量避免将自定义的
SmartInstantiationAwareBeanPostProcessor
优先级设置为最低LOWEST_PRECEDENCE
,因为两个优先级相同的类会继续根据第二维度的顺序执行。 - 执行
关于
Spring
在执行bean
实例化时为什么是将工厂实例提前暴露出去,而不是直接提前构造完earlySingletonObject
实例暴露出去呢?在笔者看来这个问题有
2
个方面的考虑:
- 从功能上讲,通过
getEarlyBeanReference
提前暴露bean
实例后可能会由于开发规范性的问题导致最终它与初始化后获得的对象不一致(具体可阅读上文)。因此Spring
只有在真正发生了循环依赖时才通过getEarlyBeanReference
提前暴露bean
实例,其他情况下则尽量避免。- 从设计上讲,通过
getEarlyBeanReference
提前暴露bean
实例会破坏了bean
实例化原有的执行流程:实例化->属性填充->实例初始化(添加AOP
代理),使得它变为:添加AOP
代理->实例化->属性填充->实例初始化,这可能会产生除了上述第1
种所产生的问题,还可能会导致其他异常的发生。因此Spring
只有在真正发生了循环依赖时才通过getEarlyBeanReference
提前暴露bean
实例,其他情况下则尽量避免(常常在迭代中会用这种手法去兼容代码,而不是直接作暴力修改处理)。这也解释了为什么
Spring
使用了三级缓存来解决循环依赖,而不是二级缓存。
最后,回到getSingleton(String, ObjectFactory)
方法在执行完createBean
方法生成bean
实例后接着执行后置处理afterSingletonCreation
将当前beanName
从singletonsCurrentlyInCreation
中移除(避免再次创建导致异常的发生),然后执行addSingleton
方法将最终完整版的bean
添加到三级缓存singletonObjects
中,同时在二级缓存earlySingletonObjects
和一级缓存singletonFactories
中将beanName
对应的缓存实例移除。
java
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
protected void addSingleton(String beanName, Object singletonObject) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
}
至此,结束单例模式(Singleton
)下bean
的创建(含循环依赖)。
多例模式的bean
创建
相比于单例模式,多例模式的bean
创建就相对来说简单得多。首先,在多例模式下没有三级缓存的查询,而是直接通过isPrototypeCurrentlyInCreation
方法判断prototypesCurrentlyInCreation
属性是否包含当前beanName
,若包含则表示循环依赖的发生,抛出BeanCurrentlyInCreationException
异常;否则表示prototypesCurrentlyInCreation
属性不包含当前beanName
,开始执行多例模式的bean
创建。下面,我们看到具体的代码步骤:
java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws ... {
String beanName = ...;
Object beanInstance;
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// ...
RootBeanDefinition mbd = ...;
// ...
// Create bean instance.
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
return adaptBeanInstance(name, beanInstance, requiredType);
}
/**
* Return whether the specified prototype bean is currently in creation
* (within the current thread).
* @param beanName the name of the bean
*/
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
与单例模式类似,在多例模式下会在bean
的创建前后加上前置处理beforePrototypeCreation
方法和后置处理afterPrototypeCreation
方法,具体代码如下所示:
java
/** Names of beans that are currently in creation. */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
/**
* Callback before prototype creation.
* <p>The default implementation register the prototype as currently in creation.
* @param beanName the name of the prototype about to be created
* @see #isPrototypeCurrentlyInCreation
*/
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
/**
* Callback after prototype creation.
* <p>The default implementation marks the prototype as not in creation anymore.
* @param beanName the name of the prototype that has been created
* @see #isPrototypeCurrentlyInCreation
*/
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
在前置处理beforePrototypeCreation
会将当前请求的beanName
加入到prototypesCurrentlyInCreation
中;而在后置处理afterPrototypeCreation
中则将当前请求的beanName
从prototypesCurrentlyInCreation
中移除。结合isPrototypeCurrentlyInCreation
方法的判断,如果在请求多例bean
(假设依赖也是多例bean
)的过程中发生了循环依赖(在多例模式下特指具有相同beanName
的bean
)则必然会发生异常(由于没有提前暴露与缓存机制,不区分是构造器自动装配还是Setter
方法自动装配)。
根据多例模式的特性,如果在遇到对同一
beanName
的循环依赖时如果不通过异常提前终止创建流程,则会造成bean
在创建过程中不断地循环创建对象。
text
+
|
|
v
+----------------+-----------------+
throw ex | |
<----------+ isPrototypeCurrentlyInCreation +<----------+
| | |
+----------------+-----------------+ |
| |
| |
v |
+-------------+--------------+ |
| | |
| beforePrototypeCreation | |
| | |
+-------------+--------------+ |
| |
| |
v |
+------+-------+ |
| | |
| createBean +---------------------+
| | circular references
+------+-------+
|
|
v
+------------+-------------+
| |
| afterPrototypeCreation |
| |
+------------+-------------+
|
|
v
回归到正常流程上,如果在前置处理beforePrototypeCreation
没有发现循环依赖,那么Spring
就会进入到createBean
方法执行bean
实例的创建。
java
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
//...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 1. 执行`raw bean`实例的创建
BeanWrapper instanceWrapper = ...createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ...
Object exposedObject = bean;
// 2. 执行`raw bean`实例的属性填充
populateBean(beanName, mbd, instanceWrapper);
// 3. 执行`raw bean`实例的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// ...
return exposedObject;
}
相比于单例模式,多例模式由于没有提前暴露和缓存的机制,createBean
方法的执行就简单得多,具体执行流程如下所示:
- 执行
raw bean
实例的创建 - 执行
raw bean
实例的属性填充 - 执行
raw bean
实例的初始化
在执行流程上看仅是保留了基本的raw bean
创建流程,那再结合上文前置方法beforePrototypeCreation
和后置方法afterPrototypeCreation
的处理,若在populateBean
属性填充时发生了循环依赖就会触发isPrototypeCurrentlyInCreation
的判断抛出BeanCurrentlyInCreationException
异常,具体可参考以下流程图:
text
+
|
|
+----------------------------------------+
| | |
| v |
| +----------------+-----------------+ |
throw ex | | | | circular references
<-----------------+ isPrototypeCurrentlyInCreation +-----------------------------------+
| | | | |
| +----------------+-----------------+ | |
| | | |
| | | |
| v | |
| +-------------+--------------+ | |
| | | | |
| | beforePrototypeCreation | | |
| | | | |
| +-------------+--------------+ | |
| | | |
| | | |
| v | |
| +------+-------+ | +----------------------+ |
| | | | | | |
| | createBean +----------------->+ createBeanInstance | |
| | | | | | |
| +------+-------+ | +----------+-----------+ |
| | | | |
| | | | |
| v | v |
| +------------+-------------+ | +-------+--------+ |
| | | | | | |
| | afterPrototypeCreation | | | populateBean +------+
| | | | | |
| +------------+-------------+ | +-------+--------+
| | | |
+----------------------------------------+ | continue
| |
v v
需要注意,上述讨论的是请求
bean
与依赖bean
都为多例模式的情况,而如果请求bean
或者依赖bean
任意一个不是多例模式(例如,单例模式)则需要结合对应作用域的bean
创建流程进行分析,由于篇幅有限就不过多展开了。
至此,结束多例模式(Prototype
)下bean
的创建(含循环依赖)。
循环依赖的结论
总的来说,如果在Spring
创建raw bean
的阶段中发生了循环依赖是无法被解决而抛出异常的,因为在这个阶段中Spring
还尚未将bean
提前暴露出去,典型的有通过构造器完成自动装配发生了循环依赖。而如果Spring
在raw bean
的属性填充阶段发生了循环依赖,则会被Spring
通过将bean
提前暴露出去的方式顺利化解掉,典型的有使用字段的自动装配和Setter
方法的自动装配来完成依赖注入。
需要注意的是,
Spring
在处理工厂方法参数的自动装配时会将它们当作是构造参数的自动装配来处理,也就是说通过工厂方法完成自动装配时发生了循环依赖也会抛出异常。
除此之外,Spring
在进行bean
的实例化过程中还是会存在一些比较隐匿比较特殊的情况会导致bean
在发生循环依赖时抛出异常。例如,在@Configuration
的配置类中存在自动装配的属性与通过实例工厂方法创建的bean
有相互依赖的关系时可能会因循环依赖抛出异常,具体如下所示:
java
@Configuration
public class Config {
@Autowired
private List<BaseService> serviceList;
@Bean
public CircularReferenceService circularReferenceService(){
return new CircularReferenceService(serviceList);
}
}
@Service
public class ServiceFirst implements BaseService {
@Autowired
private CircularReferenceService circularReferenceService;
}
@Service
public class ServiceSecond extends BaseService {
@Autowired
private CircularReferenceService circularReferenceService;
}
在上述场景中,如果ServiceFirst
或者ServiceSecond
先于Config
被加载就会发生循环依赖异常。要清晰解释其中发生异常的原理需要我们再次回顾上文《源码分析:Bean
的创建》章节中提到"关于通过实例工厂方法声明bean
"的实例化流程,即在通过实例工厂方法创建bean
实例前会提前获取当前实例工厂方法所在的工厂实例,具体代码如下所示:
java
// factoryBeanName属性指的是工厂实例bean的名称,而不是FactoryBean实例的名称
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
//...
factoryBean = this.beanFactory.getBean(factoryBeanName);
}
// ...
在这里Spring
就会使用BeanFactory#getBean
来获取工厂实例。
在有了这一层认知之后,我们结合上文关于"Bean
的创建"章节和"Bean
的循环依赖"章节的分析推演出以下执行流程:
- 执行
ServiceFirst
的bean
实例创建。 - 将早期
ServiceFirst
的bean
实例提前暴露。 - 执行
ServiceFirst#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 执行
CircularReferenceService
的创建(将其加入到了singletonsCurrentlyInCreation
属性中),由于是通过实例工厂方法声明的,所以在CircularReferenceService
创建前触发了Config
的创建(尚未将早期CircularReferenceService
的bean
实例提前暴露)。 - 执行
Config
的bean
实例创建。 - 执行
Config#serviceList
的属性填充,触发BaseService
类(含ServiceFirst
和ServiceSecond
)的创建。 - 执行
ServiceFirst
的bean
实例创建,由于在缓存中查询到被早期暴露ServiceFirst
实例(在第2
步中被暴露),所以结束ServiceFirst
的再次创建。 - 执行
ServiceSecond
的bean
实例创建。 - 执行
ServiceSecond#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建(在缓存中尚未添加CircularReferenceService
实例)。 - 执行
CircularReferenceService
的创建,由于在第4
步首次触发创建时已经将CircularReferenceService
加入到了singletonsCurrentlyInCreation
属性中,在此再次创建CircularReferenceService
时判断singletonsCurrentlyInCreation
属性中存在CircularReferenceService
,因此抛出循环依赖异常。
在上述流程中,我们可以看到由于Spring
先触发了ServiceFirst
而导致了循环依赖异常的发生;类似地,如果我们先触发了ServiceSecond
也同样会触发循环依赖异常的发生。那如果我们先触发了Config
配置类的加载是不是就不会产生问题了呢?下面我们可以来模拟一下整个流程:
- 执行
Config
的bean
实例创建。 - 将早期
Config
的bean
实例提前暴露。 - 执行
Config#serviceList
的属性填充(属性加载阶段),触发BaseService
类(含ServiceFirst
和ServiceSecond
)的创建。 - 执行
ServiceFirst
的bean
实例创建。 - 执行
ServiceFirst#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 由于
CircularReferenceService
是通过实例工厂方法声明的,所以在CircularReferenceService
创建前触发了Config
的创建。 - 执行
Config
的bean
实例创建,由于在缓存中查询到被早期暴露Config
实例(在第2
步中被暴露),所以结束Config
的再次创建。 - 执行
CircularReferenceService
的创建,将Config#serviceList
的属性传入CircularReferenceService
构造器中进行创建,但由于Config#serviceList
属性尚处于加载阶段而没有将值真正设置到属性中,所以此时传入null
到CircularReferenceService
构造器中进行构建(意味着CircularReferenceService
中的serviceList
属性为null
)。 - 完成
CircularReferenceService
的创建(含属性填充完成和实例初始化完成),并将CircularReferenceService
实例加入到缓存中。 - 完成
ServiceFirst
的bean
实例创建(含属性填充完成和实例初始化完成),并将ServiceFirst
实例加入到缓存中。 - 执行
ServiceSecond
的bean
实例创建。 - 执行
ServiceSecond#CircularReferenceService
的属性填充,触发CircularReferenceService
的创建。 - 执行
CircularReferenceService
的bean
实例创建,由于在缓存中查询到已被实例化完成的CircularReferenceService
实例(在第9
步中将构建完成的CircularReferenceService
实例加入到缓存),所以结束CircularReferenceService
的再次创建。需要注意,在缓存中CircularReferenceService
的serviceList
属性为null
。 - 完成
ServiceSecond
的bean
实例创建(含属性填充完成和实例初始化完成),并将ServiceSecond
实例加入到缓存中。 - 执行
Config#serviceList
的属性填充(属性赋值阶段),在赋值完成后Config#serviceList
的属性值为包含ServiceFirst
实例和ServiceSecond
实例的列表。 - 完成
Config
的bean
实例创建(含属性填充完成和实例初始化完成),并将Config
实例加入到缓存中。
在上述模拟流程中,我们可以看到先触发了Config
配置类的加载同样会产生其他问题,即在属性填充前期Spring
尚未将值设置到Config#serviceList
中时触发了CircularReferenceService
的加载导致传入其构造器的Config#serviceList
属性为null
,进而使得构建完成的CircularReferenceService
实例中serviceList
属性为null
(赋值失败)。显然地,这是比循环依赖更为致命的异常,因为它会在运行时让程序发生异常而不是在启动加载时。
综上所述,一般情况下如果在Spring
创建raw bean
的阶段中发生了循环依赖是无法被解决而抛出异常的,而如果Spring
在raw bean
的属性填充阶段发生了循环依赖则会被Spring
通过将bean
提前暴露出去的方式顺利化解掉。但这并不是绝对的法则,在特殊情况下属性填充阶段发生的循环依赖也是有可能会抛出异常的(甚至会产生更为致命的异常),这取决于我们在开发中是如何使用Spring IoC
的。
至此,我们完成了整个Bean
循环依赖的分析章节。