1. 程式人生 > >【String註解驅動開發】困擾了我很久的AOP巢狀呼叫終於解決了!

【String註解驅動開發】困擾了我很久的AOP巢狀呼叫終於解決了!

## 寫在前面 > 最近在分析Spring原始碼時,在同一個類中寫了巢狀的AOP方法,測試時出現:Spring AOP在同一個類裡自身方法相互呼叫時無法攔截。哎,怎麼辦?還能怎麼辦呢?繼續分析Spring原始碼,解決問題唄。於是乎,有了本文。 > > 專案工程原始碼已經提交到GitHub:[https://github.com/sunshinelyz/spring-annotation](https://github.com/sunshinelyz/spring-annotation) ## 問題闡述 Spring AOP在同一個類裡自身方法相互呼叫時無法攔截。比如下面的程式碼: ```java 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代理物件的引用,呼叫時通過這個代理即可。例如下面的程式碼。 ```java //從beanFactory取得AOP代理後的物件 SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService"); //把AOP代理後的物件設定進去 someServiceProxy.setSelf(someServiceProxy); //在someMethod裡面呼叫self的someInnerMethod,這樣就正確了 someServiceProxy.someMethod(); ``` 但這個代理物件還要我們手動set進來。有沒有更好的方式解決呢? ## 問題解決 幸好SpringBeanFactory有BeanPostProcessor擴充套件,在bean初始化前後會統一傳遞給BeanPostProcess處理,繁瑣的事情就可以交給程式了,程式碼如下,首先定義一個BeanSelfAware介面,實現了此介面的程式表明需要注入代理後的物件到自身。 ```java 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方法,把自身的代理物件注入自身。 ```java 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加進去就可以了,比平常多一行配置而已。 ```xml