SpringAOP嵌套调用的解决办法

Spring AOP在同一个类里自身方法相互调用时无法拦截。比如下面的代码:

Java代码


1. public class SomeServiceImpl implements SomeService    
   
2. {    
   
3.    
   
4.     public void someMethod()    
   
5.     {    
   
6.         someInnerMethod();    
   
7.         //foo...    
   
8.     }    
   
9.    
   
10.     public void someInnerMethod()    
   
11.     {    
   
12.         //bar...    
   
13.     }    
   
14. }

两个方法经过AOP代理,执行时都实现系统日志记录。单独使用someInnerMethod时,没有任何问题。但someMethod就有问题了。someMethod里调用的someInnerMethod方法是原始的,未经过AOP增强的。我们期望调用一次someMethod会记录下两条系统日志,分别是someInnerMethod和someMethod的,但实际上只能记录下someMethod的日志,也就是只有一条。在配置事务时也可能会出现问题,比如someMethod方法是REQUIRED,someInnerMethod方法是REQUIRES_NEW,someInnerMethod的配置将不起作用,与someMethod方法会使用同一个事务,不会按照所配置的打开新事务。
由于java这个静态类型语言限制,最后想到个曲线救国的办法,出现这种特殊情况时,不要直接调用自身方法,而通过AOP代理后的对象。在实现里保留一个AOP代理对象的引用,调用时通过这个代理即可。比如:

Java代码


1. //从beanFactory取得AOP代理后的对象    
    
2. SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService");     
   
3.    
   
4. //把AOP代理后的对象设置进去    
    
5. someServiceProxy.setSelf(someServiceProxy);     
   
6.    
   
7. //在someMethod里面调用self的someInnerMethod,这样就正确了    
    
8. someServiceProxy.someMethod();

但这个代理对象还要我们手动set进来,幸好SpringBeanFactory有BeanPostProcessor扩展,在bean初始化前后会统一传递给BeanPostProcess处理,繁琐的事情就可以交给程序了,代码如下,首先定义一个BeanSelfAware接口,实现了此接口的程序表明需要注入代理后的对象到自身。

Java代码


1. public class SomeServiceImpl implements SomeService,BeanSelfAware    
   
2.    
   
3. {    
   
4.    
   
5.     private SomeService self;//AOP增强后的代理对象    
    
6.    
   
7.      
   
8.    
   
9.     //实现BeanSelfAware接口    
    
10.    
   
11.     public void setSelf(Object proxyBean)    
   
12.    
   
13.     {    
   
14.    
   
15.         this.self = (SomeService)proxyBean    
   
16.    
   
17.     }    
   
18.    
   
19.      
   
20.    
   
21.     public void someMethod()    
   
22.    
   
23.     {    
   
24.    
   
25.         someInnerMethod();//注意这句,通过self这个对象,而不<SPAN class=hilite2>是</SPAN>直接调用的    
    
26.    
   
27.         //foo...    
   
28.    
   
29.     }    
   
30.    
   
31.     public void someInnerMethod()    
   
32.    
   
33.     {    
   
34.    
   
35.         //bar...    
   
36.    
   
37.     }    
   
38.    
   
39. }

再定义一个BeanPostProcessor,beanFactory中的每个Bean初始化完毕后,调用所有BeanSelfAware的setSelf方法,把自身的代理对象注入自身……

Java代码


1. public class InjectBeanSelfProcessor implements BeanPostProcessor    
   
2. {    
   
3.      
   
4.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException    
   
5.     {    
   
6.         if(bean instanceof BeanSelfAware)    
   
7.         {    
   
8.             System.out.println("inject proxy:" + bean.getClass());    
    
9.             BeanSelfAware myBean = (BeanSelfAware)bean;    
   
10.             myBean.setSelf(bean);    
   
11.             return myBean;    
   
12.         }    
   
13.         return bean;    
   
14.     }    
   
15.      
   
16.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException    
   
17.     {    
   
18.         return bean;    
   
19.     }    
   
20. }


最后,在BeanFactory配置中组合起来,只需要把BeanPostProcesser加进去就可以了,比平常多一行配置而已。

Java代码


1. <!-- 注入代理后的bean到bean自身的BeanPostProcessor... -->    
    
2. <bean class=" org.mypackage.InjectBeanSelfProcessor"></bean>    
   
3.    
   
4. <bean id="someServiceTarget" class="org.mypackage.SomeServiceImpl" />     
   
5.    
   
6. <bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">    
   
7.     <property name="target">    
   
8.         <ref local="someServiceTarget" />    
   
9.     </property>    
   
10.     <property name="interceptorNames">    
   
11.         <list>    
   
12.             <value>someAdvisor</value>    
   
13.         </list>    
   
14.     </property>    
   
15. </bean>    
   
16.    
   
17. <!-- 调用spring的<SPAN class=hilite1>DebugInterceptor</SPAN>记录日志,以确定方法<SPAN class=hilite2>是</SPAN>否被AOP增强 -->    
    
18. <bean id="<SPAN class=hilite1>debugInterceptor</SPAN>" class="org.springframework.aop.interceptor.<SPAN class=hilite1>DebugInterceptor</SPAN>" />    
   
19.    
   
20. <bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">    
   
21.     <property name="advice">    
   
22.         <ref local="<SPAN class=hilite1>debugInterceptor</SPAN>" />    
   
23.     </property>    
   
24.     <property name="patterns">    
   
25.         <list>    
   
26.             <value>.*someMethod</value>    
   
27.             <value>.*someInnerMethod</value>    
   
28.         </list>    
   
29.     </property>    
   
30. </bean>

这里的someService#someInnerMethod就表现出预期的行为了,无论怎样,它都是经过AOP代理的,执行时都会输出日志信息。
用XmlBeanFactory进行测试需要注意,所有的BeanPostProcessor并不会自动生效,需要执行以下代码:

Java代码


1. XmlBeanFactory factory = new XmlBeanFactory(...);    
   
2. InjectBeanSelfProcessor postProcessor = new InjectBeanSelfProcessor();    
   
3. factory.addBeanPostProcessor(postProcessor);

ft:发完帖再看论坛里之前的帖子,发现居然更新了,而且取名都叫做self……
http://www.javaeye.com/post/347986

评论

fyting 2007-08-07

yeshucheng 写道

请问能否以这个帖子的例子来具体说明呢?
http://www.javaeye.com/post/347986

相同的原理啊,只是那篇帖子的配置有几个trick的地方。首先是这段

Java代码


1. <bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">      
   
2.         <property name="proxyInterfaces"><value>aop.SomeService</value></property>      
   
3.         <property name="target"><ref local="someServiceTarget"/></property>      
   
4.         <property name="interceptorNames">      
   
5.             <list>      
   
6.                 <value>someAdvisor</value>      
   
7.             </list>      
   
8.         </property>      
   
9.   </bean>

<bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">  
        <property name="proxyInterfaces"><value>aop.SomeService</value></property>  
        <property name="target"><ref local="someServiceTarget"/></property>  
        <property name="interceptorNames">  
            <list>  
                <value>someAdvisor</value>  
            </list>  
        </property>  
  </bean>

声明proxyInterfaces为SomeService,那么在BeanPostProcessor里得到的代理后的Bean只具有SomeService接口,而没有所需的BeanSelfAware,所以不会把代理过的bean通过self注入进去。(事实上在初始化过程中,InjectBeanSelfProcessor会对someService注入两次self,第一次是BeanFactory生成someServiceTarget,第二次是产生someService的代理后)。其实不声明proxyInterfaces,SpringAOP照样跑
另外用了正则表达式配置Pointcut,这个正则表达式有问题,使用CGLIB时拦截器根本不会起作用,因为通过CGLIB代理后的类都是这样的:“aop.SomeServiceImpl$$EnhancerByCGLIB$$88d3ec43”

Java代码


1. <bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">    
   
2.       <property name="advice"><ref local="<SPAN class=hilite1>debugInterceptor</SPAN>"/></property>    
   
3.       <property name="patterns">    
   
4.           <list>    
   
5.               <value>aop/.SomeService/.someMethod</value>      
   
6.               <value>aop/.SomeService/.someInnerMethod</value>      
   
7.           </list>    
   
8.       </property>    
   
9. </bean>