您的位置:首页 > Web前端

its raw version as part of a circular reference,but not use the final version of the bean

2016-06-17 19:53 549 查看
大概的错误信息是:

org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name topicService': Bean with name ' topicService''has been injected into other beans [OperationLogAop] in its raw version as partof a circular reference, but has
eventually been wrapped. This means that saidother beans do not use the final version of the bean. This is often the resultof over-eager type matching - consider using 'getBeanNamesOfType' with the'allowEagerInit' flag turned off, for example.

猜测问题:

its raw version as part of a circular reference关键字,难道是出现了循环依赖。但是spring是支持循环依赖。而且代码上我足够相信,没有该类似的代码。do not use the final version of the bean,从字面上意义可以了解出,存在多版本的不同实例(topicService)。可能是代码上有什么隐藏问题,但是鉴于测试环境能够正式运行,因此为了先让机器能够正常启动,但是线上环境却不行

1.排查环境

1.1 jdk和tomcat

替换对应的版本,后来还是不能够正常运行

1.2怀疑内核(系统环境)影响

不过由于机器上重装系统比较麻烦,所以就没有对该步进行操作,后来运维找了另外两台机子进行部署。有问题的机器,留着继续跟踪问题

2.代码排查(真正原因排查)

2.1 找到代码出错的位置

(如图1),AbstractAutowireCapableBeanFactory.doCreateBean的方法,可以看出引起出错的原因:earlySingletonReference不为空&&exposedObject!=bean的关系引起。

earlySingletonReference:是表示在bean的初始化过程中,由于转入其它流程,可能引起beanFactory中bean的构造(如循环依赖的处理)



图1
2.2 打印日志,在本地重现bug

从代码上逆推出整个程序出错的具体原因(spring的整体框架比较复杂,而且流程较多),确实比较困难。于是通过在出错的TopicService和OperationLogAop两个类的一些初始化方法(构造方法)等加上日志。通过对日志的分析,发现有问题的机子上面:

TopicService的构造方法早于OperationLogAop的构造方法。

为了在本地重现,于是在applicationContext-ams.xml中加入了红色矩形框的代码,如图2,



图2
 

2.3 代码跟踪,springioc的核心beanfactory,对于实例对象的管理。

如图3,由于beanFactory从缓存中获取不到实例对象,从而需要对实例对象进行构建,如图4。



图3 AbstractBeanFactory.getBean方法



图4 AbstractBeanFactory.getBean获取不到实例,创建实例的代码核心
2.3看到AbstractAutowireCapableBeanFactory的方法doCreateBean

图5中:createBeanInstance类似于new XXX()这种方式来实例化对象,只不过spring经过反射之后得到

图6中:populateBean:这里会对bean的属性进行诸如@Autowired的属性注入initilizeBean:这里对bean的一些方法诸如回调(@PostConstruct)、动态代理对象的创建



图5



图6
2.4 initilizeBean代理类的创建流程(出错的入口)

由于topicService中没有@Autowired的属性,所以就不进行分析。而在topicService的方法中有@Transaction的方法拦截(如图7)和OperationLogAop中的@Around等方法(如图8),因此会在图9中获取到的specificInterceptors也就不会为空(这也是本次代码出现的关键点)。



图7 TopicService的基本源码



图8 OperationLogAop的基本源码



图9 AbstractAutoProxyCreator.wrapIfNecessary方法
 

2.4.1 在2.4获取的specificInterceptors的过程中(会按照Ordered的顺序来决定拦截的次序,不然就是按照类加载进来的顺序),如图10、图11,正常情况是不会马上对该aop的类(如@Transaction)直接进行初始化,但是这边由于OperationLogAop实现了Ordered接口(见图6),因而会引起OperationLogAop的初始化(如图12、13)。这也是本次出错的主要原因。



图10 AbstractAdvisorAutoProxyCreator.findEligibleAdvisors



图11 OrderComparator的compare方法与getOrder



图12 AbstractPointcutAdvisor的方法getOrder



图13 AbstractBeanFactoryPointcutAdvisor的getAdvice方法
2.4.2 OpeartionLogAop的初始化过程(看2.2),在populateBean的方法中如图14会对autowired的属性topicService(如图8)进行注入。由于我们在代码中采用了@Autowired的方式进行注入,所以接着会使用AutowiredAnnotationBeanPostProcessor来处理topicService的注入(如图15)。然后通过beanFactory去寻找topicService的实例(如图16)。由于此时topicService的实例还未存入beanFactory中(流程中2.4中断了,转而先创建OperationLogAop的实例),因此这时候又会新增topicService的实例对象(造成流程2.1中的earlySingletonReference不为空)。

 



图14 AbstractAutowireCapableBeanFactory 方法populateBean



图15 AutowiredAnnotationBeanPostProcessor的方法 postProcessPropertyValues



图16 DefaultListableBeanFactory的方法 findAutowireCandidates
2.5 执行了OperationLogAop的bean初始化流程之后,完成如图17的红色矩形框的流程,接着,又创建TopicService的代理对象,并返回



图17 AbstractAutoProxyCreator.wrapIfNecessary方法
2.6 于是就会出现earlySingletonReference不为空(流程2.4.2创建),exposedObject为代理对象(流程2.5创建)!=bean(由流程2.2创建),因此就会报错如图18



图18
2.7 由于以上的流程,是我们通过辅助手段分析得到的原因,并不是线上环境出现的。于是我们需要分析一下context:component-scan的类加载并实例化顺序。我们来到bean的初始化入口(如图19),循环beanNames,然后初始化里面的bean对象。而beanNames又是依赖于beanDefinitionNames。



图19 DefaultListableBeanFactory的preInstantiateSingletons
2.8往上查找,找了图19的调用入口,是在AbstractApplicationContext的refresh方法中(如图21),并且发现如图20中obtainFreshBeanFactory的方法,正是 beanDefinitionNames的来源



图20 AbstractApplicationContext的fresh方法



图21 AbstractApplicationContext的fresh方法
2.9 跟踪obtainFreshBeanFactory的方法,查找beanDefinitionNames的具体生成规则。

2.9.1 XmlWebApplicationContext.loadBeanDefinitions中如图22,循环扫描配置的资源文件信息(如图23)



图22 XmlWebApplicationContext.loadBeanDefinitions



图23 资源文件的配置信息
2.9.2 reader将对应的配置信息,转化为具体的资源访问信息(解析文件路径),如图24



图24 AbstractBeanDefinitionReader.loadBeanDefinitions
2.9.3  DefaultBeanDefinitionDocumentReader.parseBeanDefinitions解析具体的xml信息,开始生成bean的定义信息。如图25.

parseDefaultElement:http://www.springframework.org/schema/beans这个命名空间采用默认的解析方式,比如元素<bean>等

context:component-scan:不属于默认域名空间,采用了parseCustomerElement的方式来解析



图25 DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
2.9.4 NamespaceHandlerSupport的parse,又有不同域名空间的解析(如图26)

aop:aspectj-autoproxy:使用了AspectJAutoProxyBeanDefinitionParser

context:component-scan:使用了ComponentScanBeanDefinitionParser

tx:annotation-driven使用TxNamespaceHandler



图26 NamespaceHandlerSupport.parse
2.9.5 ComponentScanBeanDefinitionParser.findCa…扫描包名下面的类文件,并将其解析成BeanDefinition,如图27



图27 ComponentScanBeanDefinitionParser.findCandidateComponents
 

2.9.6 通过PathMatchingResourcePatternResolver.findPathMatchingResources,来扫描路径的Class信息,如图28



图28 PathMatchingResourcePatternResolver.findPathMatchingResources
2.9.7 最后在PathMatchingResourcePatternResolver.doRetrieveMatchingFiles中,通过

File.listFiles来扫描获取对应的Class文件,如图29



图29 PathMatchingResourcePatternResolver.findPathMatchingResources
总结:

出现以上问题的原因,可以归结为以下几点:

1.    采用了context:component-scan来加载类实例的话,会因为Java的Api File.listFIles的文件顺序来决定类的实例顺序(windows是名称字母的排序。在linux上面目前没有发现什么规律,1.不是时间;2不是名称;3不是大小)

2.    OperationLogAop实现了Ordered的接口。导致了在TopicService初始化的时候,加载aop代理类,就会将OperationLogAop实例化

3.    OperationLogAop注解了TopicService的实例。由于2中TopicService的初始化还未完成,就因为OperationLogAop的初始化又初始化了TopicService的实例。导致beanFactory工厂中有多个version的TopicService对象

[如有转发,请注明转载]

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息