Spring中的增強型別
Spring通過增強型別定義橫切邏輯,同時由於Spring只支援方法連線點,增強還包括了在方法的哪一點加入橫切程式碼的方位資訊,所以增強既包括橫切邏輯,還包括部分連線點的資訊。
增強包括以下幾類:
前置增強:org.springframework.aop.BeforeAdvice代表前置增強,表示在目標方法整形前實施增強
後置增強:org.springframework.aop.AfterReturningAdvice代表後置增強,表示在目標方法執行後實施增強
環繞增強:org.springframework.aop.MethodInterceptor代表環繞增強,表示在目標方法執行前後實施增強
異常丟擲增強 :org.springframework.aop.ThrowsAdvice代表丟擲異常增強,表示在目標方法丟擲異常後實施增強
引介增強:org.springframework.aop.IntroductionInterceptor代表引介增強,表示在目標類中新增一些新的方法和屬性
前置增強
下面通過一個服務生服務前的禮貌用語來舉例增強:
Waiter.java
public interface Waiter {
void greetTo(String name);
void serveTo(String name);
}
未新增前置增強前:
NaiveWaiter.java
public class NaiveWaiter implements Waiter {
public void greetTo(String name) {
System.out.println("greet to "+name+"...");
}
public void serveTo(String name){
System.out.println("serving "+name+"...");
}
}
前置增強類如下:
GreetingBeforeAdvice.java
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you!Mr."+clientName+".");
}
}
MethodBeforeAdvice是前置增強BeforeAdvice介面的子類。其中僅有唯一的方法:before(Method method, Object[] args, Object obj) throws Throwable,method為目標類的方法,args為目標類方法的入參,obj為目標類的例項,當該方法發生異常時,將會阻止目標類方法的執行。
前置增強應用如下:
TestBeforeAdvice.java
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
//建立增強
BeforeAdvice advice = new GreetingBeforeAdvice();
//Spring提供的代理工廠
ProxyFactory pf = new ProxyFactory();
//指定對介面進行代理
pf.setInterfaces(target.getClass().getInterfaces());
//啟用優化
pf.setOptimize(true);
//設定代理目標
pf.setTarget(target);
//為代理目標新增增強
pf.addAdvice(advice);
//生成代理例項
Waiter proxy = (Waiter)pf.getProxy();
proxy.greetTo("John");
proxy.serveTo("Tom");
}
}
執行結果如下:可以發現每個方法執行前都引入了前置增強的語句
在上面的代理工廠中就是通過JDK代理或CGLib代理的技術,如果通過setInterfaces方法指定對介面進行代理就會使用JdkDynamicAopProxy,如果是針對類的代理,則使用Cglib2AopProxy。同時,如上如果啟動了優化代理的方式,針對介面的代理也會使用Cglib2AopProxy。
在上面的原始碼中可以通過ProxyFactory的addAdvice()方法新增一個增強,使用者可以新增多個增強,多個增強的呼叫順序和新增順序一致,也可以通過addAdvice(int Advice)將增強新增到增強鏈的具體位置(第一個位置為0)
Spring中配置如下:
<bean id="greetingBefore" class="com.baobaotao.advice.GreetingBeforeAdvice" />
<bean id="target" class="com.baobaotao.advice.NaiveWaiter" />
<bean id="waiter"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter" p:target-ref="target"
p:interceptorNames="greetingAdvice"/>
ProxyFactoryBean的常用屬性如下:
target:代理的目標物件
proxyInterfaces:代理所要實現的介面,可以是多個介面。屬性的另一個別名interfaces
interceptorNames:需要植入目標的Bean列表,採用Bean的名稱指定,指定使用的增強。這些Bean必須是實現了org.aopalliance.intercept.MethodInterceptor或org.springframework.aop.Advisor的Bean,即就是一個增強
singleton:返回的代理是否是單例項,預設為單例項
optimize:設定為true時,強制使用CGLib代理,對於singleton的代理,推薦使用CGLib,因為CGLib建立代理速度慢,而創建出的物件執行效率高,JDK代理的正好相反
proxyTargetClass:是否對類進行代理,設定為true時,使用CGLib代理
測試增強的程式碼如下:
public class TestAdvice1 {
public static void main(String[] args) {
String configPath = "com/baobaotao/advice/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
}
}
後置增強
後置增強在目標類方法呼叫後執行如下新建一個後置增強
GreetingAfterAdvice.java
public class GreetingAfterAdvice implements AfterReturningAdvice {
//在目標方法呼叫後執行
public void afterReturning(Object returnObj, Method method, Object[] args,
Object obj) throws Throwable {
System.out.println("Please enjoy yourself!");
}
}
後置增強需要實現AfterReturningAdvice來定義後置增強的邏輯,AfterReturningAdvice介面定義了唯一的方法afterReturning(Object returnObj, Method method, Object[] args, Object obj) throws Throwable,returnObj為目標類例項。如果在後置增強中丟擲異常,若該異常是目標方法生命的異常,則該異常歸併到目標方法中;如果不是目標方法所生命的異常,則將其作為執行期異常丟擲
環繞增強
環繞增強綜合實現了前置、後置增強兩者的功能。
如下示例:
GreetingInterceptor.java
public class GreetingInterceptor implements MethodInterceptor {
//用來截獲目標方法的執行,並在前後新增橫切邏輯
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] args = invocation.getArguments();
String clientName = (String)args[0];
//目標方法執行前的呼叫
System.out.println("How are you!Mr."+clientName+".");
//通過反射機制呼叫目標方法
Object obj = invocation.proceed();
//目標方法執行後的呼叫
System.out.println("Please enjoy yourself!");
return obj;
}
}
如上原始碼所示,MethodInterceptor作為環繞增強的介面。該介面擁有唯一的介面方法Object invoke(MethodInvocation var1) throws Throwable;
MethodInvocation封裝了目標方法、入引數組以及目標方法所在的例項物件,通過getArguments()可以獲得目標方法的入引數組,通過proceed()反射呼叫目標例項相應的方法。
異常丟擲增強
異常丟擲增強適合事務管理的場景,這裡用一個模擬出錯丟擲異常,來使用異常丟擲增強的例子:
ForumService.java
public class ForumService {
public void removeForum(int forumId) {
// do sth...
throw new RuntimeException("執行異常。");
}
public void updateForum(Forum forum) throws Exception{
// do sth...
throw new SQLException("資料更新操作異常。");
}
}
如下定義一個異常丟擲增強:
TransactionManager.java
public class TransactionManager implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) throws Throwable {
System.out.println("-----------");
System.out.println("method:" + method.getName());
System.out.println("丟擲異常:" + ex.getMessage());
System.out.println("成功回滾事務。");
}
}
afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable
ThrowsAdvice 異常增強介面並無任何方法,是一個標識介面,在執行期間使用反射機制自行判斷,採用方法名為afterThrowing的方法定義增強方法。方法入參中前三個是可選的(一起選或不選),最後一個引數為Throwable或其子類,可以在一個異常丟擲增強中同時定義多個afterThrowing(), Spring會選擇最匹配的增強方法,即就是類的繼承關係越近相似度越高,匹配度就越高。
例如,若定義了afterThrowing(SQLException e)和afterThrowing(Throwable e);而丟擲了SQLException時就會匹配afterThrowing(SQLException e)。
引介增強
引介增強並非在目標方法周圍織入增強,而是為目標類建立新的方法和屬性,所以引介增強的連線點是類級別的,而非方法級別的,通過引介增強,可以為目標類新增一個介面的實現。如下面這個例子:
Monitorable.java
public interface Monitorable {
void setMonitorActive(boolean active);
}
ControllablePerformaceMonitor.java
public class ControllablePerformaceMonitor
extends
DelegatingIntroductionInterceptor implements Monitorable {
private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
public void setMonitorActive(boolean active) {
MonitorStatusMap.set(active);
}
public Object invoke(MethodInvocation mi) throws Throwable {
Object obj = null;
if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
PerformanceMonitor.begin(mi.getClass().getName() + "."
+ mi.getMethod().getName());
obj = super.invoke(mi);
PerformanceMonitor.end();
} else {
obj = super.invoke(mi);
}
return obj;
}
}
第一段原始碼定義了一個介面,第二段原始碼定義了一個引介增強,繼承了DelegatingIntroductionInterceptor類,實現了Monitorable介面,在類中實現了setMonitorActive方法,該方法設定了類中的一個變數,這個變數用來控制是否開啟效能監控功能。
實現介面的方法配合攔截的方法就可以支援效能監視可控代理。
引介增強的配置如下:
<bean id="pmonitor" class="com.baobaotao.introduce.ControllablePerformaceMonitor" />
<bean id="forumServiceTarget" class="com.baobaotao.introduce.ForumService" />
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.baobaotao.introduce.Monitorable"
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true" />
引介增強的配置需要制定引介增強所實現的介面,其次需要將proxyTargetClass設定為true。
其他類的程式碼和本文開頭的類一致,引介增強測試類如下:
TestIntroduce.java
public class TestIntroduce {
public static void main(String[] args) {
String configPath = "beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
ForumService forumService = (ForumService)ctx.getBean("forumService");
forumService.removeForum(10);
forumService.removeTopic(1022);
Monitorable moniterable = (Monitorable)forumService;
moniterable.setMonitorActive(true);
forumService.removeForum(10);
forumService.removeTopic(1022);
}
}
執行結果如下:
從執行結果可以看出在未開啟效能監控時是按照正常的邏輯執行的,開啟效能監控之後就橫切入了增強的邏輯。