一. 前言
在看此文章之前,你可能已經知道了,Spring是通過Before、After、AfterRunning、AfterThrowing以及Around 共5中通知方式爲目標方法增加切面功能,比如一個需要在目標類執行一個目標方法之前和之後分別打印一份日誌,就可以建立一個切面在這個方法前後打印日誌。
但是如果我想在此目標類中再增加一個目標方法是,該怎麼辦呢? 最簡單的辦法就是在建立此目標類的時候,增加此方法。但是如果原目標類非常複雜,動一發而牽全身,不適合更改源碼的時候, 我們可以爲需要添加的方法建立一個類,然後建一個代理類,同時代理該類和目標類。
如下圖所示:
使用Spring AOP,我們可以爲bean引入新的方法,代理攔截調用並委託給實現該方法的其他對象。
圖中,A就是原目標類,B就是新添加的方法所在的類,通過建立一個代理類同時代理A和B,調用者調用該代理時,就可以同時A和B中的方法了。
二. 例子
還是一如既往的使用以前所用到的例子觀衆觀看演員表演 ,我們現在的需求是:
希望增加演員在表演結束後,給觀衆致謝這個流程.(前提是不修改源代碼)
解決辦法:
當然你可以使用切面編程的後置返回通知來實現該功能,不過本文主要討論的是另外一種新方式:利用前面編程的引用功能爲目標類中增加新的目標方法來實現,具體參考代碼實現.
原接口和實現類(A類):
public interface IPerformance {
boolean perform(String programName);
}
public class Performance implements IPerformance {
@Override
public boolean perform(String programName) {
System.out.println("開始表演節目:" + programName);
System.out.println("表演節目結束!");
return true;
}
}
增強接口和增強接口默認實現類(B類):
public interface IEnhancePerformance {
void thank();
}
public class EnhancePerformance implements IEnhancePerformance {
@Override
public void thank() {
System.out.println("感謝大家的觀看!");
}
}
代理Schema配置:
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="false"></aop:aspectj-autoproxy>
<bean id="performance" class="com.yveshe.Performance" />
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.yveshe.IPerformance+" implement-interface="com.yveshe.aop.IEnhancePerformance" default-impl="com.yveshe.aop.EnhancePerformance"/>
</aop:aspect>
</aop:config>
Spring引入允許爲目標對象引入新的接口,通過在< aop:aspect>
標籤內使用< aop:declare-parents>
標籤進行引入,定義方式如下:
<aop:declare-parents
types-matching="AspectJ語法類型表達式"
implement-interface=引入的接口"
default-impl="引入接口的默認實現"
delegate-ref="引入接口的默認實現Bean引用"/>
types-matching: 匹配需要引入接口的目標對象的AspectJ語法類型表達式;"+"表示IPerformance的所有子類;
implement-interface: 定義需要引入的接口;
default-impl和delegate-ref: 定義引入接口的默認實現,二者選一,default-impl是接口的默認實現類全限定名,而delegate-ref是默認的實現的委託Bean名;
測試
這兩個測試方法等價
@Test
public void shouldAnswerWithTrue() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/com/yveshe/contextconf/application-context.xml");
com.yveshe.Performance performance = context.getBean(com.yveshe.Performance.class);
String programName = "不良人";
performance.perform(programName);
System.out.println();
IEnhancePerformance ePerformance = (IEnhancePerformance) performance;
ePerformance.thank();
context.close();
}
@Test
public void shouldAnswerWithTrue2() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/com/yveshe/contextconf/application-context.xml");
com.yveshe.Performance performance = context.getBean(com.yveshe.Performance.class);
String programName = "不良人";
performance.perform(programName);
System.out.println();
IEnhancePerformance ePerformance = context.getBean("performance", IEnhancePerformance.class);
ePerformance.thank();
context.close();
}
開始表演節目:不良人
表演節目結束!
感謝大家的觀看!
分析:
1)目標對象類型匹配:使用types-matching="com.yveshe.IPerformance+"匹配IPerformance接口的子類型,如Performance實現;
2)引入接口定義:通過implement-interface屬性表示引入的接口,如“com.yveshe.aop.IEnhancePerformance”。
3)引入接口的實現:通過default-impl屬性指定,如“com.yveshe.aop.EnhancePerformance”,也可以使用“delegate-ref”來指定實現的Bean。
4)獲取引入接口:如使用“IEnhancePerformance ePerformance = context.getBean(“performance”, IEnhancePerformance.class);”可直接獲取到引入的接口。
本文例子:
https://github.com/YvesHe/spring-yveshe/tree/master/spring-aspect-schema-introductions
另外基於Java註解方式的例子:(@DeclareParents)
https://github.com/YvesHe/spring-yveshe/tree/master/spring-aspect-introductions