AOP聯盟為增強定義了org.aopalliance.aop.Advice接口,Spring支持5種類型的增強:
1)前置增強:org.springframework.aop.BeforeAdvice 代表前置增強,因為Spring 只支持方法級的增強,所有MethodBeforeAdvice是目前可用的前置增強,表示在目標方法執行前實施增強,而BeforeAdvice是為了將來版本擴展需要而定義的;
2)后置增強:org.springframework.aop.AfterReturningAdvice 代表后增強,表示在目標方法執行后實施增強;
3)環繞增強:org.aopalliance.intercept.MethodInterceptor 代表環繞增強,表示在目標方法執行前后實施增強;
4)異常拋出增強:org.springframework.aop.ThrowsAdvice 代表拋出異常增強,表示在目標方法拋出異常后實施增強;
5)引介增強:org.springframework.aop.IntroductionInterceptor 代表引介增強,表示在目標類中添加一些新的方法和屬性。
1、前置增強
模擬服務員向顧客表示歡迎和對顧客提供服務。
Waiter接口:
package com.yyq.advice; public interface Waiter { void greetTo(String name); void serveTo(String name); }
NaiveWaiter服務類:
package com.yyq.advice; public class NaiveWaiter implements Waiter { @Override public void greetTo(String name) { System.out.println("greet to " + name + "..."); } @Override public void serveTo(String name) { System.out.println("serving to " + name + "..."); } }
前置增強實現類:
package com.yyq.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { String clientName = (String) objects[0]; System.out.println("How are you ! Mr." + clientName + "."); } }
測試方法:
@Test public void testBeforeAdvice(){ Waiter target = new NaiveWaiter(); BeforeAdvice advice = new GreetingBeforeAdvice(); ProxyFactory pf = new ProxyFactory(); //Spring提供的代理工廠 pf.setTarget(target); //設置代理目標 pf.addAdvice(advice); Waiter proxy = (Waiter)pf.getProxy(); //生成代理實例 proxy.greetTo("John"); proxy.serveTo("Tom"); }
輸出結果:
How are you ! Mr.John.
greet to John...
How are you ! Mr.Tom.
serving to Tom...
在Spring中配置:beans.xml
<bean id="greetingAdvice" class="com.yyq.advice.GreetingBeforeAdvice"/> <bean id="target" class="com.yyq.advice.NaiveWaiter"/> <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingAdvice" p:target-ref="target"/>
ProxyFactoryBean 是FactoryBean接口的實現類。
· target:代理的目標對象;
· proxyInterfaces:代理所實現的接口,可以是多個接口。該屬性還有一個別名屬性interfaces;
· interceptorNames:需要織入目標對象的Bean列表,采用Bean的名稱指定。這些Bean必須是實現了org.aopalliance.intercept.Method 或 org.springframework.aop.Advisor的Bean,配置中的順序對應調用的順序;
· singleton:返回的代理是否是單實例,默認為單實例;
· optimize:當設置為true時,強制使用CGLib代理。對於singleton的代理,我們推薦使用CGLib,對於其他作用域類型的代理,最好使用JDK代理。原因是CGLib創建代理時速度慢,而創建出的代理對象運行效率較高,而使用JDK代理的表現正好相反;
· proxyTargetClass:是否對類進行代理(而不是對接口進行代理),設置為true時,使用CGLib代理。
測試方法:
@Test public void testBeforeAdvice2(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter1"); waiter.greetTo("Joe"); }
輸出結果:
How are you ! Mr.Joe.
greet to Joe...
2、后置增強
后置增強在目標類方法調用后執行。模擬服務員在每次服務后使用禮貌用語。
GreetingAfterAdvice后置增強實現類:
package com.yyq.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class GreetingAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o2) throws Throwable { System.out.println("Please enjoy yourself."); } }
在beans.xml文件添加后置增強:
<bean id="greetingBefore" class="com.yyq.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.yyq.advice.GreetingAfterAdvice"/> <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingBefore,greetingAfter" p:target-ref="target"/>
測試方法:
@Test public void testBeforeAndAfterAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter2"); waiter.greetTo("Joe"); }
結果輸出:
How are you ! Mr.Joe.
greet to Joe...
Please enjoy yourself.
3、環繞增強
環繞增強允許在目標類方法調用前后織入橫切邏輯,它綜合實現了前置、后置增強兩者的功能。
GreetingInterceptor環繞增強實現類:
package com.yyq.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object[] args = methodInvocation.getArguments(); String clientName = (String) args[0]; System.out.println("Hi,Mr " + clientName + "."); Object obj = methodInvocation.proceed(); System.out.println("Please enjoy yourself~"); return obj; } }
Spring 直接使用AOP聯盟所定義的MethodInterceptor作為環繞增強的接口。該接口擁有唯一的接口方法 Object invoke(MethodInvocation invocation), MethodInvocation不但封裝目標方法及其入參數組,還封裝了目標方法所在的實例對象,通過MethodInvocation的getArguments()可以獲取目標方法的入參數組,通過proceed()反射調用目標實例相應的方法。
在beans.xml文件添加環繞增強:
<bean id="greetingAround" class="com.yyq.advice.GreetingInterceptor"/> <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingAround" p:target-ref="target"/>
測試方法:
@Test public void testAroundAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter3"); waiter.greetTo("Joe"); }
結果輸出:
Hi,Mr Joe.
greet to Joe...
Please enjoy yourself~
4、異常拋出增強
異常拋出增強最適合的應用場景是事務管理,當參與事務的某個Dao發送異常時,事務管理器就必須回滾事務。
Forum業務類:
package com.yyq.advice; public class Forum { private int forumId; public int getForumId() { return forumId; } public void setForumId(int forumId) { this.forumId = forumId; } }
ForumService業務類:
package com.yyq.advice; import java.sql.SQLException; public class ForumService { public void removeForum(int forumId){ System.out.println("removeForum...."); throw new RuntimeException("運行異常"); } public void updateForum(Forum forum)throws Exception{ System.out.println("updateForum"); throw new SQLException("數據更新操作異常。"); } }
TransactionManager異常拋出增強實現類:
package com.yyq.advice; import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; 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("成功回滾事務。"); } }
ThrowAdvice異常拋出增強接口沒有定義任何方法,它是一個標識接口,在運行期Spring使用反射的機制自行判斷,我們采用以下簽名形式定義異常拋出的增強方法:void afterThrowing(Mehod method, Object[] args, Object target, Throwable);方法名必須為afterThrowing,方法入參規定,前三個參數是可選的,要么三個入參提供,要么不提供,最后一個入參是Throwable或者子類。
在beans.xml文件添加異常拋出增強:
<bean id="transactionManager" class="com.yyq.advice.TransactionManager"/> <bean id="forumServiceTarget" class="com.yyq.advice.ForumService"/> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="transactionManager" p:target-ref="forumServiceTarget" p:proxyTargetClass="true"/>
測試方法:
@Test public void testThrowsAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumService fs = (ForumService)ctx.getBean("forumService"); try{ fs.removeForum(10); } catch (Exception e) {} try{ fs.updateForum(new Forum()); } catch (Exception e) {} }
結果輸出:
----------------
method:removeForum
拋出異常:運行異常
成功回滾事務。
updateForum
----------------
method:updateForum
拋出異常:數據更新操作異常。
成功回滾事務。
5、引介增強
引介增強為目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別的。通過引介增強,我們可以為目標類添加一個接口的實現,即原來目標類未實現某個接口,通過引介增強可以為目標類創建實現某個接口的代理。Spring定義了引介增強接口IntroductionInterceptor,該接口沒有定義任何的方法,Spring為該接口提供了DelegatingIntroductionInterceptor實現類。
Monitorable:用於標識目標類是否支持性能監視的接口
package com.yyq.advice; public interface Monitorable { void setMonitorActive(boolean active); }
ControllablePerformanceMonitor 為引介增強實現類:
package com.yyq.advice; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable { private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>(); @Override 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; } }
PerformanceMonitor監視類:
package com.yyq.advice; public class PerformanceMonitor { private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); public static void begin(String method) { System.out.println("begin monitor..."); MethodPerformance mp = new MethodPerformance(method); performanceRecord.set(mp); } public static void end(){ System.out.println("end monitor..."); MethodPerformance mp = performanceRecord.get(); mp.printPerformance(); } }
MethodPerformance記錄性能信息:
public class MethodPerformance { private long begin; private long end; private String serviceMethod; public MethodPerformance(String serviceMethod){ this.serviceMethod = serviceMethod; this.begin = System.currentTimeMillis(); } public void printPerformance(){ end = System.currentTimeMillis(); long elapse = end - begin; System.out.println(serviceMethod + "花費" + elapse + "毫秒。"); } }
在beans.xml文件添加引介增強:
<bean id="pmonitor" class="com.yyq.advice.ControllablePerformanceMonitor"/> <bean id="forumServiceImplTarget" class="com.yyq.advice.ForumServiceImpl"/> <bean id="forumService2" class="org.springframework.aop.framework.ProxyFactoryBean" p:interfaces="com.yyq.advice.Monitorable" p:interceptorNames="pmonitor" p:target-ref="forumServiceImplTarget" p:proxyTargetClass="true"/>
測試方法:
@Test public void testIntroduce(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumServiceImpl forumServiceImpl = (ForumServiceImpl)ctx.getBean("forumService2"); forumServiceImpl.removeForum(23); forumServiceImpl.removeTopic(1023); Monitorable monitorable = (Monitorable)forumServiceImpl; monitorable.setMonitorActive(true); forumServiceImpl.removeForum(22); forumServiceImpl.removeTopic(1023); }
結果輸出:
模擬刪除Forum記錄:23
模擬刪除Topic記錄:1023
begin monitor...
模擬刪除Forum記錄:22
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeForum花費40毫秒。
begin monitor...
模擬刪除Topic記錄:1023
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeTopic花費21毫秒。