Spring自定義BeanPostProcessor的時候怎麼支持循環依賴

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對象不是一個。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章