用spring ProxyFactoryBean做AOP,拦截器会执行2的问题(转http://alipaymiddleware.com/spring/%e7%94%a8spring-proxyfactorybean%e5%81%9aaop%ef%bc%8c%e6%8b%a6%e6%88%aa%e5%99%a8%e4%bc%9a%e6%89%a7%e8%a1%8c2%e7%9a%84%e9%97%ae%e9%a2%98/)

Coordinator
Jul 16, 2013 at 7:48 AM
用spring ProxyFactoryBean做AOP,拦截器会执行2的问题

作者 严羽 在 3 六月 2013, 4:14 下午417 浏览数


问题描述:

昨天和今天有同学反馈:使用xts调用参与者会报错“原子业务处理请求中的业务活动Id不允许人工指定”。



经过调试,发现这样的配置:
<bean id="revtransServiceForTddl" class="org.springframework.aop.framework.ProxyFactoryBean">

   <property name="proxyInterfaces" 

       value="com.alipay.revtrans.common.service.facade.api.RevtransService" />

   <property name="target" ref="revtransServiceTargetForTddl" /> 

   <property name="interceptorNames"> 

       <list> 

          <value>businessActionInterceptorForTddl</value> 

       </list> 

   </property> 

</bean> 


在最后生成的revtransServiceForTddl对象(这是一个代理对象),拦截器businessActionInterceptorForTddl会被advise(“织入”)2次:





这就意味着当调用参与者时,BusinessActionInterceptor的invoke方法会执行2次,第一次填充了txId,第二次填充之前会做检查,发现已经填充了,所以报错“原子业务处理请求中的业务活动Id不允许人工指定”。





为什么会被“织入”2次?

因为被代理的对象(revtransServiceTargetForTddl)和拦截器(businessActionInterceptorForTddl)产生了循环依赖。

在spring bean初始化的时候,由于对象之间的依赖关系,在初始化revtransServiceTargetForTddl 时,ProxyFactoryBean的initializeAdvisorChain方法,会进入2次,导致织入了2次拦截器。





相同的用法,不同的系统(charge vs income),为什么一个好使,一个不好使?同一个系统,同一套代码(income)为什么有的同学机器上好使,有的不好使?

Spring在初始化时,解析xml文件,形成一个beandefinition map,然后循环这个map进行bean的初始化。

因为map是不能保证顺序的,导致不同机器初始化bean的顺序不同,所以导致了不同的结果。



解决办法:

因为map的顺序是不可预知的,所以这种场景(bean有循环依赖,并且用到了ProxyFactoryBean的时候),

我们就不要使用ProxyFactoryBean了,改用org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator来做AOP的拦截器织入。



一个例子:

修改前:
<bean id="revtransServiceForTddl" class="org.springframework.aop.framework.ProxyFactoryBean">

   <property name="proxyInterfaces" 

       value="com.alipay.revtrans.common.service.facade.api.RevtransService" />

   <property name="target" ref="revtransServiceTargetForTddl" /> 

   <property name="interceptorNames"> 

       <list> 

          <value>businessActionInterceptorForTddl</value> 

       </list> 

   </property> 

</bean> 
修改后:
<bean 

   class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

   <property name="beanNames"> 

       <list> 

          <value>revtransServiceForTddl</value> 

       </list> 

   </property> 

   <property name="interceptorNames"> 

       <list> 

          <value>businessActionInterceptorForTddl</value> 

       </list> 

   </property> 

</bean>