1. 什麼是循環依賴?
循環依賴說白了就是在我們的程序中,類A需要依賴於類B,類B又需要依賴於類A,這時候兩個類實例就構成了相互依賴,你中有我,我中有你。
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
2. Spring什麼情況下可以幫助我們解決循環依賴?
我們知道在spring中,如果我們的bean是單例的,並且不是通過構造方法進行依賴注入,spring大部分情況下是可以幫助我麼解決循環依賴問題的,當然也有
可能解決不了。
(具體參考:這個Spring循環依賴的坑,90%以上的人都不知道)。
3. Prototype類型爲什麼解決不了循環依賴?
在spring的AbstractBeanFactory中的doGetBean方法中有一個判斷,如果是Prototype類型並且該實例正在創建中,直接拋出異常。
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
/**
* 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))));
}
4. 構造方法注入爲什麼解決不了循環依賴?
思考一個邏輯,spring創建A的時候,發現A的構造函數中需要注入B,然後spring去創建B,這時候發現,B的構造函數中又需要注入A,可是這時候A還在等待B創
建成功後進行注入,A還沒創建好呢,這時候就構成了死等待,A等待B創建,B等待A創建,最終的結果就是都進行不下去了,spring檢測到後就直接給我們拋異常
了。
5. 自定義BeanPostProcessor
這時候如果我們想要自定義BeanPostProcessor,並且可能需要通過代理原始類,擴展我們的功能,那麼這時候,如果我們像下面這樣寫:
@Component
public class TestBeanPostProcessor implements BeanPostProcessor,MethodInterceptor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof A){
System.out.println("對A創建代理");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(A.class);
enhancer.setCallback(this);
return enhancer.create();
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理攔截" + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
}
這樣你只能收穫到無情的報錯:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name
'a' has been injected into other beans [b] 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.
這段錯誤大概的意思就是,我們已經注入的bean後來被修改了,導致注入的bean和最終實際的bean不是一個,所以報錯了,當然,你也可以按照提示,通過
allowEagerInit這個屬性進行配置,允許這種不一致的情況發生,只是我不推薦這種做法,很有可能帶來莫名其妙的錯誤。
下面我們來改寫一下實現方式
@Component
public class TestBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,MethodInterceptor{
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
if(bean instanceof A){
System.out.println("對A創建代理");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(A.class);
enhancer.setCallback(this);
return enhancer.create();
}
return bean;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理攔截" + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
}
你會發現這種方式成功啓動了,並且在B中注入的A實例就是我們代理後的對象。
6. 爲什麼第二種方式能成功呢?
注意一下這兩種實現方式,第一種是實現了BeanPostProcessor,並對postProcessBeforeInitialization方法進行了重寫;第二種是實現了
SmartInstantiationAwareBeanPostProcessor,並對getEarlyBeanReference進行了重寫。要想明白原因,我們需要知道spring在什麼時候回觸發
BeanPostProcessor的調用。
之所以第二種能夠成功,是因爲Spring中提前暴露bean時的一段處理邏輯,這段邏輯位於AbstractAutowireCapableBeanFactory中的doCreateBean方法中。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
...
// 如果允許循環依賴,並且當前bean正在創建中,那麼先放入singletonFactories,singletonFactories屬於是一個緩存,spring獲取bean
// bean實例的時候,會先從singletonObjects獲取,獲取不到再從earlySingletonObjects獲取,如果還沒有,就會從singletonFactories
// 進行獲取,因爲提前暴露的對象我們已經add到singletonFactories中了,那麼從singletonFactories獲取對象時就會調用
// () -> getEarlyBeanReference(beanName, mbd, bean),從而就可以提前獲取到代理對象了
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");
}
// 這裏是重點,裏面封裝了能夠正確注入代理的邏輯
// 重點就是getEarlyBeanReference(beanName, mbd, bean)方法
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 在這個方法裏面,會執行AutowiredAnnotationBeanPostProcessor的postProcessProperties,從而進行了@Autowird屬性的注入
populateBean(beanName, mbd, instanceWrapper);
// 在這個方法裏面,會觸發第一種BeanPostProcessor的postProcessBeforeInitialization方法的調用
exposedObject = initializeBean(beanName, exposedObject, mbd);
}catch (Throwable ex) {
...
}
if (earlySingletonExposure) {
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);
}
}
// 這裏就是我們上面報錯的地方,因爲檢測出來了依賴的bean和實際的bean不一致
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;
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 從這裏就可以發現,對於提前暴露的bean引用,這裏會調用SmartInstantiationAwareBeanPostProcessor的
//getEarlyBeanReference的方法,並將該方法返回的對象作爲實際暴露的對象,那麼就不難理解爲什麼我們重寫了這個方法後,就可以將我們代
//理後的對象正確注入了
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
現在我們來看一看第一種方式爲什麼不能成功。通過上面代碼的標註,我麼知道了第一種方式postProcessBeforeInitialization方法的調用是在initializeBean中,而
我們的@Autowird註解的注入是在initializeBean的前一個方法populateBean中,也就是B中都已經注入了A提前暴露出來的對象後,我們的
postProcessBeforeInitialization方法又將A對象實例給改成了我們的代理對象,從而導致了注入的A的bean對象和最終的A的bean對象不是一個。