探討spring AOP在子類實現接口并繼承父類時失效的原因
在開發過程中,當我們使用Spring AOP對一個子類進行增強時,如果該子類同時實現了接口并繼承了父類,可能會遇到AOP切點失效的問題。讓我們通過一個具體案例來分析這種情況的表現及其原因。
問題描述
假設我們有一個類結構如下:
public interface Run { public void runs(); }
public abstract class GrandFather { public abstract void say(); }
public abstract class Parent extends GrandFather implements Run { public abstract void test(); public void test2(){ System.out.println("父類方法"); } }
@Component public class Child extends Parent { @OpenDefaultNameMark public void test(){ System.out.println("Child"); } @Override @OpenDefaultNameMark public void test2() { System.out.println("子類方法"); } @Override @OpenDefaultNameMark public void say() { } @Override public void runs() { // this.say(); } }
在上述結構中,Child類既繼承了Parent類,又實現了Run接口。同時,Child類重寫了父類的一些方法,并在這些方法上使用了自定義注解@OpenDefaultNameMark來進行AOP增強。然而,我們發現當Child類實現了Run接口的方法runs()時,AOP切點似乎失效了。
問題分析
為了驗證AOP是否真的失效,我們嘗試刪除Child類中實現的runs()方法:
@Component public class Child extends Parent { @OpenDefaultNameMark public void test(){ System.out.println("Child"); } @Override @OpenDefaultNameMark public void test2() { System.out.println("子類方法"); } @Override @OpenDefaultNameMark public void say() { } // @Override // public void runs() { // // this.say(); // } }
通過刪除runs()方法,我們發現AOP切點再次生效。這表明問題確實與Child類實現的接口方法有關。
問題原因
從AOP的實現機制來看,這種情況的問題出在AOP代理的生成方式上。Spring AOP使用動態代理來生成AOP代理類,當一個類同時實現接口和繼承類時,Spring會選擇基于接口的JDK動態代理,而不是基于類的CGLIB代理。而JDK動態代理是基于接口的代理機制,這可能會導致對類方法的AOP增強失效。
此外,根據你的描述,AOP的AspectJ代碼并沒有問題,問題的出現可能是由于插件的限制。插件在分析SpringBootApplicationContext時,依賴于Actuator的beans接口的返回值,這可能會導致插件無法準確反映AOP代理的實際情況。為了確保插件能夠正確反映AOP的生效情況,你需要在idea中啟動一次項目,使插件能夠刷新并獲取最新的AOP代理信息。
結論
在Spring AOP中,當一個子類既實現了接口又覆蓋了父類的方法時,AOP切點可能會失效。這是因為Spring可能選擇了基于接口的JDK動態代理,而這種代理機制可能無法正確處理類方法的AOP增強。為了解決這個問題,可以嘗試使用CGLIB代理,或者在IDEA中啟動項目以確保插件能夠正確反映AOP的實際效果。