在使用spring的场景中,有时会碰到如下的一种情况,即bean之间的循环引用。并且,在一般情况下,这个配置在现有的spring3.0中是可以正常工作的,前提是没有对beanA和beanB进行增强。但是,如果任意一方进行了增强,比如通过spring的代理对beanA进行了增强,即实际返回的对象和原始对象不一致的情况,在这种情况下,就会报如下错误:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'chuguanService': Bean with name 'chuguanService' has been injected into other beans [orderFinishService] 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,其所引用的对象并不是由spring容器最终生成的对象,而只是一个原始对象,而spring不允许这种情况出现,即持有过程中间对象。从上面的异常中很清楚的看到有两个service,chuguanService以及orderFinishService,这两个bean互相引用,就出现了循环引用的问题。正常情況下,spring对这种scope单例循环引用是支持的,但是这里报错是因为我的项目中多了一些其他的配置。在項目的aop配置文件中配置了
<bean class="com.jd.oa.supplier.edw.ump.UmpMonitorProcessor">
<property name="filterPath">
<value>classpath:/ump/filter.xml</value>
</property>
<property name="umpAppName" value="vps-edw"/>
</bean>
在filter.xml配置文件中又配置了一些bean,其中包括chuguanService,最终结果是配置文件中的bean会再做一层动态代理。目的是采用动态代理的形式对项目中的代码统一增加UMP的监控。项目中的UmpMonitorProcessor.class会实现BeanPostProcessor,并且重写了postProcessAfterInitialization方法,代码如下:
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
...........................................
//如果实现了接口的bean,使用java动态代理来生成代理。
if(beaninstanceof BaseTaskTemplate || (!S.isNull(beanProxyStrategy.get(beanName)) && !beanProxyStrategy.get(beanName))){
//如果没有实现任何接口,那么使用cglib来生成代理
UmpMonitorCglibProxy proxy =new UmpMonitorCglibProxy();
proxy.setBeanName(beanName);
proxy.setAppName(umpAppName);
Object proxy0 = proxy.getInstance(bean);
proxy.setMethods(beanMethodNames.get(beanName));
return proxy0;}
return bean;
}
那么spring是如何处理bean间引用,以及使用何种策略处理循环引用的呢?简单说一下其实就是利用三级缓存的形式。Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,Spring为了解决单例的循环依赖问题,使用了三级缓存。这三级缓存分别指:
singletonFactories : 三级缓存,单例对象工厂的cache
earlySingletonObjects :二级缓存,提前曝光的单例对象的Cache
singletonObjects:一级缓存,单例对象的cache
Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取。当发生循环引用时,beanA首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象beanB,此时就尝试去get(B),发现B还没有被创建,所以走beanB的创建流程,beanB在初始化第一步的时候发现自己依赖了对象beanA,于是尝试get(A),尝试一级缓存singletonObjects,发现没有,因为beanA还没初始化完全,尝试二级缓存earlySingletonObjects也没有,尝试三级缓存singletonFactories,由于beanA通过ObjectFactory将自己提前曝光了,所以beanB能够通过ObjectFactory.getObject拿到beanA对象,beanB拿到beanA对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回beanA中,beanA此时能拿到beanB的对象顺利完成自己的初始化阶段2、3,最终beanA也完成了初始化,放入到一级缓存singletonObjects中,这是正常情况下spring解决单例bean循环引用的方案。但是,当出现项目中的配置之后会出现无法解决循环引用的问题,
下面再看一下创建bean的方法。其中,AbstractAutowireCapableBeanFactory类中的的doCreateBean方法。其中一段代码:
在initializeBean方法中,会调用applyBeanPostProcessorsAfterInitialization方法,方法中会执行postProcessAfterInitialization方法
也就是会调用我们UmpMonitorProcessor的postProcessAfterInitialization方法。继续回到doCreateBean方法中,接下来会执行一段校验。
上面有4个判断点
判断点1,首先确定这个对象能从earlySingletonObjects中取出对象来,经过上面的分析,我们知道,在正常情况下,此对象为null,即不存在循环检测。而在循环引用中,此对象能够被取出来。
判断点2,再判断这个对象和当前通过beanPostProcessor处理过的对象是否相同,如果相同,表示对象没有经过修改,即A=A-,那么循环引用成立。无需处理
判断点3,判断当前对象A是否被其他对象所依赖,在循环引用中,已经处理了A和B,那么在依赖表中,即在属性dependentBeanMap和dependenciesForBeanMap中。其中A->B表示A依赖于B,B->A表示B依赖于A。那么在dependentBeanMap中就会出现两个entry,分别为A->B和B->A。这里A依赖于B,那么表示A已经被依赖,则进入进一步检测中。在检测中,将取得一个A的被依赖列表中的bean已经被创建的对象列表值。
判断点4,如果被依赖对象列表不为空,则表示出现循环引用。因为按照创建规则,如果A->B,则必须先创建B,而B->A,则必须先创建A。在这里,A被B依赖,就要求A必须在B之前被创建,而B又被A依赖,又要求A必须在B之前被创建。这创建的两个对象必须满足一致才可以。即在A->B中的两个对象,必须和B->A的两个对象,互相一致才可以,否则就不是循环引用。
在上面这个校验过程中,在对A进行验证时,就会从earlySingletonObjects中取得一个A,但是这个A和后面的A-可能不是同一个对象,这是因为有了beanPostProcessor存在,它可以改变bean的最终值,比如对原始bean进行封装,代理等。在这个过程中,出现了3个对象A,A-,B,而B中所持有的A对象为原始的A。如果这里的A和A-不是同一个对象,即产生了beanA有了beanB的引用,但beanB并没有beanA的引用,而是另一个beanA的引用。这肯定不满足循环引用的条件,所以项目启动会报上面的异常日志。
解决方案是,可以采用AOP切面的形式,对需要加UMP监控的类以及方法增加配置。
文章参考地址:https://www.iflym.com/index.php/code/201208280001.html