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

冰河團隊發表於2020-06-29

寫在前面

最近在分析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

相關文章