Spring循環(huán)依賴正確性及Bean注入的順序關(guān)系詳解
一、前言
我們知道 Spring 可以是懶加載的,就是當(dāng)真正使用到 Bean 的時(shí)候才實(shí)例化 Bean。當(dāng)然也不全是這樣,例如配置 Bean 的 lazy-init 屬性,可以控制 Spring 的加載時(shí)機(jī)?,F(xiàn)在機(jī)器的性能、內(nèi)存等都比較高,基本上也不使用懶加載,在容器啟動(dòng)時(shí)候來(lái)加載bean,啟動(dòng)時(shí)間稍微長(zhǎng)一點(diǎn)兒,這樣在實(shí)際獲取 bean 供業(yè)務(wù)使用時(shí),就可以減輕不少負(fù)擔(dān),這個(gè)后面再做分析。 我們使用到 Bean 的時(shí)候,最直接的方式就是從 Factroy 中獲取,這個(gè)就是加載 Bean 實(shí)例的源頭。
最近在做項(xiàng)目時(shí)候遇到一個(gè)奇葩問(wèn)題,就是bean依賴注入的正確性與bean直接注入的順序有關(guān)系,但是正常情況下明明是和順序沒(méi)關(guān)系的啊,究竟啥情況那,不急,讓我一一道來(lái)。
二、普通Bean循環(huán)依賴-與注入順序無(wú)關(guān)
2.1 循環(huán)依賴?yán)优c原理
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public BeanA getBeanA() {
return beanA;
}
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
<bean id="beanA" class="com.alibaba.test.circle.BeanA"> <property name="beanB"> <ref bean="beanB" /> </property> </bean>
<bean id="beanB" class="com.alibaba.test.circle.BeanB"> <property name="beanA"> <ref bean="beanA" /> </property> </bean>
上述循環(huán)依賴注入能夠正常工作,這是因?yàn)镾pring提供了EarlyBeanReference功能,首先Spring里面有個(gè)名字為singletonObjects的并發(fā)map用來(lái)存放所有實(shí)例化并且初始化好的bean,singletonFactories則用來(lái)存放需要解決循環(huán)依賴的bean信息(beanName,和一個(gè)回調(diào)工廠)。當(dāng)實(shí)例化beanA時(shí)候會(huì)觸發(fā)getBean(“beanA”);首先看singletonObjects中是否有beanA有則返回:
(1)
Object sharedInstance = getSingleton(beanName);//getSingleton(beanName,true);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 如果是普通bean直接返回,工廠bean則返回sharedInstance.getObject();
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
一開(kāi)始肯定沒(méi)有所以會(huì)實(shí)例化beanA,如果設(shè)置了allowCircularReferences=true(默認(rèn)為true)并且當(dāng)前bean為單件并且該bean目前在創(chuàng)建中,則初始化屬性前把該bean信息放入singletonFactories單件map里面:
(2)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
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);
}
}
}
然后對(duì)該實(shí)例進(jìn)行屬性注入beanB,屬性注入時(shí)候會(huì)getBean(“beanB”) ,發(fā)現(xiàn)beanB 不在singletonObjects中,就會(huì)實(shí)例化beanB,然后放入singletonFactories,然后進(jìn)行屬性注入beanA,然后觸發(fā)getBean(“beanA”);這時(shí)候會(huì)到(1)getSingleton返回實(shí)例化的beanA。到此beanB初始化完畢添加beanB 到singletonObjects然后返回,然后beanA 初始化完畢,添加beanA到singletonObjects然后返回
2.2 允許循環(huán)依賴的開(kāi)關(guān)
public class TestCircle2 {
private final static ClassPathXmlApplicationContext moduleContext;
private static Test test;
static {
moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
moduleContext.setAllowCircularReferences(false);
test = (Test) moduleContext.getBean("test");
}
public static void main(String[] args) {
System.out.println(test.name);
}
}
ClassPathXmlApplicationContext類中有個(gè)屬性allowCircularReferences用來(lái)控制是否允許循環(huán)依賴默認(rèn)為true,這里設(shè)置為false后發(fā)現(xiàn)循環(huán)依賴還是可以正常運(yùn)行,翻看源碼:
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
this(configLocations, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
知道默認(rèn)構(gòu)造ClassPathXmlApplicationContext時(shí)候會(huì)刷新容器。
refresh方法會(huì)調(diào)用refreshBeanFactory:
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 創(chuàng)建bean工廠
DefaultListableBeanFactory beanFactory = createBeanFactory();
//定制bean工廠屬性
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException(
"I/O error parsing XML document for application context [" + getDisplayName() + "]", ex);
}
}
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding.booleanValue());
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences.booleanValue());
}
}
到這里就知道了,我們?cè)谡{(diào)用 moduleContext.setAllowCircularReferences(false)前,spring留出的設(shè)置bean工廠的回調(diào)customizeBeanFactory已經(jīng)執(zhí)行過(guò)了,最終原因是,調(diào)用設(shè)置前,bean工廠已經(jīng)refresh了,所以測(cè)試代碼改為:
public class TestCircle {
private final static ClassPathXmlApplicationContext moduleContext;
private static Test test;
static {
//初始化容器上下文,但是不刷新容器
moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"},false);
moduleContext.setAllowCircularReferences(false);
//刷新容器
moduleContext.refresh();
test = (Test) moduleContext.getBean("test");
}
public static void main(String[] args) {
System.out.println(test.name);
}
}
現(xiàn)在測(cè)試就會(huì)拋出異常:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
三、工廠Bean與普通Bean循環(huán)依賴-與注入順序有關(guān)
3.1 測(cè)試代碼
工廠bean
public class MyFactoryBean implements FactoryBean,InitializingBean{
private String name;
private Test test;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DependentBean getDepentBean() {
return depentBean;
}
public void setDepentBean(DependentBean depentBean) {
this.depentBean = depentBean;
}
private DependentBean depentBean;
public Object getObject() throws Exception {
return test;
}
public Class getObjectType() {
// TODO Auto-generated method stub
return Test.class;
}
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
public void afterPropertiesSet() throws Exception {
System.out.println("name:" + this.name);
test = new Test();
test.name = depentBean.doSomething() + this.name;
}
}
為了簡(jiǎn)化,只寫(xiě)一個(gè)public的變量
public class Test {
public String name;
}
public class DependentBean {
public String doSomething(){
return "hello:";
}
@Autowired
private Test test;
}
xml配置
<bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <bean class="com.alibaba.test.circle.DependentBean"></bean> </property> <property name="name" value="zlx"></property> </bean>
其中工廠Bean MyFactoryBean作用是對(duì)Test類的包裝,首先對(duì)MyFactoryBean設(shè)置屬性,然后在MyFactoryBean的afterPropertiesSet方法中創(chuàng)建一個(gè)Test實(shí)例,并且設(shè)置屬性,實(shí)例化MyFactoryBean最終會(huì)調(diào)用getObject方法返回創(chuàng)建的Test對(duì)象。這里MyFactoryBean依賴了DepentBean,而depentBean本身有依賴了Test,所以這是個(gè)循環(huán)依賴
測(cè)試:
public class TestCircle2 {
private final static ClassPathXmlApplicationContext moduleContext;
private static Test test;
static {
moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
test = (Test) moduleContext.getBean("test");
}
public static void main(String[] args) {
System.out.println(test.name);
}
}
結(jié)果:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.alibaba.test.circle.DependentBean#1c701a27': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.alibaba.test.circle.Test com.alibaba.test.circle.DependentBean.test; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'test': FactoryBean which is currently in creation returned null from getObject
3.2 分析原因
當(dāng)實(shí)例化test時(shí)候會(huì)觸發(fā)getBean(“test”) ,會(huì)看當(dāng)前bean是否存在
不存在則創(chuàng)建Test 的實(shí)例,創(chuàng)建完畢后會(huì)把當(dāng)前bean信息放入singletonFactories單件map里面
然后對(duì)該實(shí)例進(jìn)行屬性注入depentBean,屬性注入時(shí)候會(huì)getBean(“depentBean”) ,
發(fā)現(xiàn)depentBean 不存在,就會(huì)實(shí)例化depentBean,然后放入singletonFactories,
然后進(jìn)行autowired注入test,然后觸發(fā)getBean(“test”);這時(shí)候會(huì)到(1)getSingleton返回實(shí)例化的test。由于test是工廠bean所以返回test.getObject();
而MyFactoryBean的afterPropertiesSet還沒(méi)被調(diào)用,所以test.getObject()返回null.
下面列下Spring bean創(chuàng)建的流程:
getBean()->創(chuàng)建實(shí)例->autowired->set屬性->afterPropertiesSet
也就是調(diào)用getObject方法早于afterPropertiesSet方法被調(diào)用了。
那么我們修改下MyFactoryBean為如下:
public Object getObject() throws Exception {
// TODO Auto-generated method stub
if(null == test){
afterPropertiesSet();
}
return test;
}
public void afterPropertiesSet() throws Exception {
if(null == test){
System.out.println("name:" + this.name);
test = new Test();
test.name = depentBean.doSomething() + this.name;
}
}
也就是getObject內(nèi)部先判斷不如test==null那調(diào)用下afterPropertiesSet,然后afterPropertiesSet內(nèi)部如果test==null在創(chuàng)建Test實(shí)例,看起來(lái)貌似不錯(cuò),好想可以解決我們的問(wèn)題。但是實(shí)際上還是不行的,因?yàn)閍fterPropertiesSet內(nèi)部使用了depentBean,而此時(shí)depentBean=null。
3.3 思考如何解決
3.2分析原因是先創(chuàng)建了MyFactoryBean,并在在創(chuàng)建MyFactoryBean的過(guò)程中有創(chuàng)建了DepentBean,而創(chuàng)建DepentBean時(shí)候需要autowired MyFactoryBean的實(shí)例,然后要調(diào)用afterPropertiesSet前調(diào)用getObject方法所以返回null。
那如果先創(chuàng)建DepentBean,然后在創(chuàng)建MyFactoryBean那?下面分析下過(guò)程:
首先會(huì)實(shí)例化DepentBean,并且加入到singletonFactories
DepentBean實(shí)例會(huì)autowired Test,所以會(huì)先創(chuàng)建Test實(shí)例
創(chuàng)建Test實(shí)例,然后加入singletonFactories
Test實(shí)例會(huì)屬性注入DepentBean實(shí)例,所以會(huì)getBean(“depentBean”);
getBean(“depentBean”) 發(fā)現(xiàn)singletonFactories中已經(jīng)有depentBean了,則返回depentBean對(duì)象
因?yàn)閐epentBean不是工廠bean所以直接返回depentBean
Test實(shí)例會(huì)屬性注入DepentBean實(shí)例成功,Test實(shí)例初始化OK
DepentBean實(shí)例會(huì)autowired Test實(shí)例OK
按照這分析先創(chuàng)建DepentBean,然后在實(shí)例化MyFactoryBean是可行的,修改xml為如下:
<bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> <bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <ref bean="dependentBean" /> </property> <property name="name" value="zlx"></property> </bean>
測(cè)試運(yùn)行結(jié)果:
name:zlx
hello:zlx
果真可以了,那按照這分析,上面XML配置如果調(diào)整了聲明順序,肯定也是會(huì)出錯(cuò)的,因?yàn)閠est創(chuàng)建比dependentBean早,測(cè)試下果然如此。另外可想而知工廠bean循環(huán)依賴工廠bean時(shí)候無(wú)論聲明順序如何必然也會(huì)失敗。
3.3 一個(gè)思考
上面先注入了MyFactoryBean中需要使用的dependentBean,然后注入MyFactoryBean,問(wèn)題就解決了。那么如果需要在另外一個(gè)Bean中使用創(chuàng)建的id=”test”的對(duì)象時(shí)候,這個(gè)Bean該如何注入那?
類似下面的方式,會(huì)成功?留給大家思考^^
public class UseTest {
@Autowired
private Test test;
}
<bean id="useTest" class="com.alibaba.test.circle.UseTest"></bean> <bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> <bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <ref bean="dependentBean" /> </property> <property name="name" value="zlx"></property> </bean>
四、 總結(jié)
普通Bean之間相互依賴時(shí)候Bean注入順序是沒(méi)有關(guān)系的,但是工廠Bean與普通Bean相互依賴時(shí)候則必須先實(shí)例化普通bean,這是因?yàn)楣SBean的特殊性,也就是其有個(gè)getObject方法的緣故。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
mybatis打印SQL,并顯示參數(shù)的實(shí)例
這篇文章主要介紹了mybatis打印SQL,并顯示參數(shù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Java?超詳細(xì)講解類的定義方式和對(duì)象的實(shí)例化
Java是一門(mén)純面向?qū)ο蟮恼Z(yǔ)言(Object?Oriented?Program,繼承OOP),在面對(duì)對(duì)象的世界里面,一切皆為對(duì)象。面向?qū)ο笫墙鉀Q問(wèn)題的一種思想,主要依靠對(duì)象之間的交互完成一件事情2022-03-03
Java如何通過(guò)Socket同時(shí)發(fā)送文本和文件
這篇文章主要介紹了Java如何通過(guò)Socket同時(shí)發(fā)送文本和文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Mybatis分頁(yè)插件PageHelper的配置和簡(jiǎn)單使用方法(推薦)
在使用Java Spring開(kāi)發(fā)的時(shí)候,Mybatis算是對(duì)數(shù)據(jù)庫(kù)操作的利器了。這篇文章主要介紹了Mybatis分頁(yè)插件PageHelper的配置和使用方法,需要的朋友可以參考下2017-12-12
Java協(xié)程編程之Loom項(xiàng)目實(shí)戰(zhàn)記錄
這篇文章主要介紹了Java協(xié)程編程之Loom項(xiàng)目嘗鮮,如果用嘗鮮的角度去使用Loom項(xiàng)目,可以提前窺探JVM開(kāi)發(fā)者們是如何基于協(xié)程這個(gè)重大特性進(jìn)行開(kāi)發(fā)的,這對(duì)于提高學(xué)習(xí)JDK內(nèi)核代碼的興趣有不少幫助,需要的朋友可以參考下2021-08-08
Java中短路運(yùn)算符與邏輯運(yùn)算符示例詳解
這篇文章主要給大家介紹了關(guān)于Java中短路運(yùn)算符與邏輯運(yùn)算符的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

