1.什么是循環(huán)依賴(lài)
假設(shè)Spring容器中有兩個(gè)Bean:A和B
依賴(lài)關(guān)系如下:
A->B->A
@Component
public class CircularA {
@Autowired
private CircularB b;
public CircularA() {
}
public void setB(CircularB b) {
this.b = b;
}
}
@Component
public class CircularB {
@Autowired
private CircularA a;
public CircularB() {
}
public void setA(CircularA a) {
this.a = a;
}
}
Spring容器在創(chuàng)建BeanA的時(shí)候,發(fā)現(xiàn)需要依賴(lài)BeanB桥状,那么在創(chuàng)建BeanB的時(shí)候冒窍,發(fā)現(xiàn)需要依賴(lài)BeanA俯树,如此就形成循環(huán)依賴(lài)闰歪。
2. Spring怎么解決循環(huán)依賴(lài)
在網(wǎng)上有很多相關(guān)的博客解釋Spring如何解決循環(huán)依賴(lài)Spring解決循環(huán)依賴(lài)。
簡(jiǎn)而言之就是Spring通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴(lài)蝌蹂。在Spring容器初始化過(guò)程中噩斟,通過(guò)beanName獲取Bean的優(yōu)先級(jí)依次是:一級(jí)緩存->二級(jí)緩存->三級(jí)緩存
/** Cache of singleton objects: bean name --> bean instance */
/** 一級(jí)緩存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
/** 三級(jí)緩存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
/** 三級(jí)緩存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
當(dāng)BeanFactory實(shí)例化BeanA后,BeanFactory會(huì)把剛剛實(shí)例化還沒(méi)有依賴(lài)注入的Bean包裝成一個(gè)ObjectFactory對(duì)象放入到三級(jí)緩存中孤个,并且從二級(jí)緩存中移除剃允,代碼如下
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
接下來(lái)進(jìn)行依賴(lài)注入,如果存在循環(huán)依賴(lài)齐鲤,例如A->B->A的情況硅急,A實(shí)例化完畢,注入A.b的時(shí)候佳遂,要實(shí)例化B,發(fā)現(xiàn)依賴(lài)a撒顿,這個(gè)時(shí)候就要從BeanFactory中獲取a實(shí)例丑罪,這個(gè)時(shí)候,緩存升級(jí)了。下面方法的第二個(gè)參數(shù)是true
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
/** 將獲取到的Bean從三級(jí)緩存中移除吩屹,并且升級(jí)到二級(jí)緩存中 */
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
b實(shí)例自己先完成實(shí)例化和依賴(lài)注入(這個(gè)時(shí)候a實(shí)例只是剛剛實(shí)例化跪另,但是已經(jīng)可以滿足beanB的需求了)以及初始化等聲明周期,最后在返回到a的創(chuàng)建流程中煤搜,a實(shí)例就可以注入已經(jīng)成熟的b實(shí)例,a實(shí)例自身也順利完成創(chuàng)建免绿,由于b實(shí)例持有了a實(shí)例的引用,所以在后續(xù)的使用中是完全沒(méi)有問(wèn)題的擦盾。
如果Spring中不存在Bean的循環(huán)依賴(lài)嘲驾,應(yīng)該是不存在從三級(jí)緩存升級(jí)到二級(jí)緩存的場(chǎng)景,因?yàn)镾pring是單線程初始化的迹卢。
這樣Spring解決Bean循環(huán)依賴(lài)的問(wèn)題A晒省!腐碱!
3. BeanPostProcessor
BeanPostProcessor接口是用來(lái)對(duì)bean進(jìn)行后置處理的誊垢,這個(gè)時(shí)候bean已經(jīng)完成實(shí)例化和依賴(lài)注入了,屬于bean初始化生命周期的一部分症见。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
如果在BeanPostProcessor的接口中喂走,對(duì)傳入的bean進(jìn)行了處理導(dǎo)致返回的bean和傳入的bean不是同一個(gè)bean,這個(gè)正常情況是沒(méi)有問(wèn)題谋作,很多中間件都是這么做的
但是S蟪Α!瓷们!當(dāng)Spring 循環(huán)依賴(lài)遇上BeanProcessor返回一個(gè)不一致對(duì)象的時(shí)候业栅,就會(huì)發(fā)生問(wèn)題了!C巍碘裕!
4. 異常情況
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxx': Bean with name 'xxx' has been injected into other beans [a,b,c] 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.
問(wèn)題的描述就是這個(gè)樣子了,大致的意思就是xxx這個(gè)bean已經(jīng)注入到很多bean中了攒钳,只不過(guò)呢依賴(lài)xxx的bean中引用的不是它的最終版本帮孔,因?yàn)樗麄冎g存在循環(huán)依賴(lài)的問(wèn)題,在解決循環(huán)依賴(lài)中使用的是二級(jí)緩存中的early bean不撑,而解決完循環(huán)依賴(lài)后文兢,bean的引用發(fā)生了變化,導(dǎo)致了early bean和 expose bean不相等焕檬,所以拋出異常了D芳帷!实愚!
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<String>(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.");
}
}
}
5. 解決方案
找到問(wèn)題原因了兼呵,那么問(wèn)題的解決通常就有了
項(xiàng)目中盡量避免Spring的循環(huán)引用兔辅,這本來(lái)就是不合理的。
使用@Lazy加載機(jī)制來(lái)解決