寫在前面
最近在分析Spring源碼時,在同一個類中寫了嵌套的AOP方法,測試時出現:Spring AOP在同一個類里自身方法相互調用時無法攔截。哎,怎么辦?還能怎么辦呢?繼續分析Spring源碼,解決問題唄。於是乎,有了本文。
項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation
問題闡述
Spring AOP在同一個類里自身方法相互調用時無法攔截。比如下面的代碼:
public class SomeServiceImpl implements SomeService {
public void someMethod() {
someInnerMethod();
}
public void someInnerMethod(){
}
}
兩個方法經過AOP代理,執行時都實現系統日志記錄。單獨使用someInnerMethod時,沒有任何問題。但someMethod就有問題了。someMethod里調用的someInnerMethod方法是原始的,未經過AOP增強的。我們期望調用一次someMethod會記錄下兩條系統日志,分別是someInnerMethod和someMethod的,但實際上只能記錄下someMethod的日志,也就是只有一條。在配置事務時也可能會出現問題,比如someMethod方法是REQUIRED,someInnerMethod方法是REQUIRES_NEW,someInnerMethod的配置將不起作用,與someMethod方法會使用同一個事務,不會按照所配置的打開新事務。
問題分析
由於java這個靜態類型語言限制,最后想到個曲線救國的辦法,出現這種特殊情況時,不要直接調用自身方法,而通過AOP代理后的對象。在實現里保留一個AOP代理對象的引用,調用時通過這個代理即可。例如下面的代碼。
//從beanFactory取得AOP代理后的對象
SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService");
//把AOP代理后的對象設置進去
someServiceProxy.setSelf(someServiceProxy);
//在someMethod里面調用self的someInnerMethod,這樣就正確了
someServiceProxy.someMethod();
但這個代理對象還要我們手動set進來。有沒有更好的方式解決呢?
問題解決
幸好SpringBeanFactory有BeanPostProcessor擴展,在bean初始化前后會統一傳遞給BeanPostProcess處理,繁瑣的事情就可以交給程序了,代碼如下,首先定義一個BeanSelfAware接口,實現了此接口的程序表明需要注入代理后的對象到自身。
public class SomeServiceImpl implements SomeService,BeanSelfAware{
//AOP增強后的代理對象
private SomeService self;
//實現BeanSelfAware接口
public void setSelf(Object proxyBean){
this.self = (SomeService)proxyBean
}
public void someMethod(){
//注意這句,通過self這個對象,而不是直接調用的
someInnerMethod();
}
public void someInnerMethod(){
}
}
再定義一個BeanPostProcessor,beanFactory中的每個Bean初始化完畢后,調用所有BeanSelfAware的setSelf方法,把自身的代理對象注入自身。
public class InjectBeanSelfProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
if(bean instanceof BeanSelfAware){
System.out.println("inject proxy:" + bean.getClass());
BeanSelfAware myBean = (BeanSelfAware)bean;
myBean.setSelf(bean);
return myBean;
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
return bean;
}
}
最后,在BeanFactory配置中組合起來,只需要把BeanPostProcesser加進去就可以了,比平常多一行配置而已。
<!-- 注入代理后的bean到bean自身的BeanPostProcessor... -->
<bean class=" org.mypackage.InjectBeanSelfProcessor"></bean>
<bean id="someServiceTarget" class="org.mypackage.SomeServiceImpl" />
<bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="someServiceTarget" />
</property>
<property name="interceptorNames">
<list>
<value>someAdvisor</value>
</list>
</property>
</bean>
<!-- 調用spring的DebugInterceptor記錄日志,以確定方法是否被AOP增強 -->
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor" />
<bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="debugInterceptor" />
</property>
<property name="patterns">
<list>
<value>.*someMethod</value>
<value>.*someInnerMethod</value>
</list>
</property>
</bean>
這里的someService#someInnerMethod就表現出預期的行為了,無論怎樣,它都是經過AOP代理的,執行時都會輸出日志信息。
注意事項
用XmlBeanFactory進行測試需要注意,所有的BeanPostProcessor並不會自動生效,需要執行以下代碼:
XmlBeanFactory factory = new XmlBeanFactory(...);
InjectBeanSelfProcessor postProcessor = new InjectBeanSelfProcessor();
factory.addBeanPostProcessor(postProcessor);
好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!
項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation
寫在最后
如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring注解驅動開發。公眾號回復“spring注解”關鍵字,領取Spring注解驅動開發核心知識圖,讓Spring注解驅動開發不再迷茫。
參考:iteye.com/blog/fyting-109236