spring的bean定义真的和顺序无关?
在使用Ibatis的时候,如果某个sql的定义出现在引用sql的定义之后的话,笨笨的ibatis是会报错的。。这让用惯了spring的人会感到烦躁,为什么ibatis不能和spring一样,做到xml定义的时候与顺序无关。。。但是 spring 真的能够做到完全与bean定义的顺序无关么?下面的代码,会让我们警醒下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="byName"> <bean id="a" ref="targetA" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean> <bean id="targetA" /><bean id="beforeAdvisor" ref="beforeAdvice"/></bean><bean id="b" ref="targetB" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean></beans>
?一个简单的循环引用 + ProxyFactoryBean 定义。
启动spring的代码:
public class Main {public static void main(String[] args) throws IOException{ApplicationContext ac = new ClassPathXmlApplicationContext("/files/reference.xml");InterfaceA a = (InterfaceA) ac.getBean("a");a.ok();}}?接着就是悲剧的error:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': org.springframework.beans.factory.FactoryBeanNotInitializedException: Cannot determine target class for proxyat org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:147)at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:109)at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1387)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:244)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:189)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByName(AbstractAutowireCapableBeanFactory.java:1085)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1035)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:511)... 39 more
??好,那么我们换下bean定义的顺序吧:
<bean id="targetA" ref="targetA" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean> <bean id="targetB" /><bean id="beforeAdvisor" ref="beforeAdvice"/></bean><bean id="b" ref="targetB" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean>
?再run一次:
2010-7-4 23:09:09 org.springframework.context.support.AbstractApplicationContext prepareRefresh信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@32fb4f: startup date [Sun Jul 04 23:09:09 CST 2010]; root of context hierarchy2010-7-4 23:09:09 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions信息: Loading XML bean definitions from class path resource [files/reference.xml]2010-7-4 23:09:09 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1d7ad1c: defining beans [targetA,a,targetB,beforeAdvice,beforeAdvisor,b]; root of factory hierarchyxxx$Proxy1
?
成功了。。。。。
?
duo xi die ? 这是为什么呢?
?
这里的问题确实是出在bean定义的顺序上面。applicationContext 在refresh的时候,是按照bean定义的顺序来加载singleton bean 的(除开BeanPostProcessor,BeanPostProcessor 在singleton加载之前被bean化)。如果仅仅只是加载普通的Bean或者普通的FactoryBean的话,Spring已经通过某种方式很好的解决了singleton循环依赖的问题。
?
导致这个问题的原因,是由2个原因叠加产生的:
1??FactoryBean的getObject(),需要开发者自己去做处理。如果是new 个对象,那么这个对象就享受不到spring 的IOC 的好处,因为它脱离了spring 容器的管辖。 而ProxyFactoryBean 的 getObject() 在获取代理对象的时候,会对target的属性有依赖,如果target的某些值为空,会抛错。(这里的target 通指 targetName,target)
?
2 spring 再 bean化 bean的时候,是分为2步的,第一步可以简单的认为new个对象出来,第二步为这个new出来的对象设置属性。就是去容器里面把这个bean需要的其他bean给注入进来。在第2步的时候,注入给其他的bean的bean 可能没有被完全bean化,很有可能只是完成了第一步的bean,还是个“半成品”。但是在整个容器初始化结束的时候,这些“半成品”bean会被变成“合格品”。
?
1 + 2 ,恩,就是ProxyFactoryBean在getObject()的时候,依赖了一个“半成品”,结果就悲剧了。
private synchronized Object getSingletonInstance() {if (this.singletonInstance == null) {this.targetSource = freshTargetSource();if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {// Rely on AOP infrastructure to tell us what interfaces to proxy.Class targetClass = getTargetClass();if (targetClass == null) {throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");}setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));}// Initialize the shared singleton instance.super.setFrozen(this.freezeProxy);this.singletonInstance = getProxy(createAopProxy());}return this.singletonInstance;}?这里的targetSource 是ProxyFactoryBean 在设置target属性的时候被设置进去的,很可惜的是,这个时候的 target 指向的Bean?还在创建中,无法走到调用setTarget() 这一步。所以,这个时候的targetSource 就是个 EMPTY_TARGET_SOURCE,它返回的targetClass? 就是null. 所以就失败了。。。。
?
原因找到了,那么怎么解决呢?
解决也有2个方法,就是针对导致问题的2个原因作出处理么?
A方案:
针对 ProxyFactoryBean 在getObject的时候,一定要求 targetSource的targetClass不为空,那么我就让他fresh下吧。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="byName"> <bean id="a" value="targetA" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean> <bean id="targetA" /><bean id="beforeAdvisor" ref="beforeAdvice"/></bean><bean id="b" value="targetB" /><property name="interceptorNames"><list><value>beforeAdvisor</value></list></property></bean></beans>
?把 target,换成targetName 就可以了
?
B方案:
因为spring 在解决循环依赖的时候,化了个好大好大的圈,以至于我们需要的某个属性迟迟还没有set进来。
那么我们可以把这个大大的圈分成N个小小的圈吗。。
既然spring初始化singleton bean是从xml文件第一个bean开始的(除开BeanPostProcessor),那么我们就好好利用下这个第一个Bean么。。就把bean定义的位置给换下吧。。
?
?
spring 还是和bean定义的顺序有关的。希望大家在使用spring的时候,有所警醒。这个几率的问题不是很大,在使用ProxyFactoryBean 的时候最好使用 targetName 属性。另外,在使用FactoryBean的时候,需要借鉴下ProxyFactoryBean所导致的问题,对于某些属性依赖的话,不要太相信spring会在那个时候给你设置进去。
1 楼 javapub 2012-02-09 Thanks, 研究的很深入嘛。 其实说Spring加载bean是没有顺序的,因为spring确实为了这个做了好多。 而你用的org.springframework.aop.framework.ProxyFactoryBean,是spring架构内的一个类,多少有点入侵spring架构的意思,多以导致了加载失败。 如果一般的bean都是自己写的,基本没有问题的。