AOP(Aspect-Oriented Programming,面向切面編程),經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。
能夠理解爲是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構(能夠理解爲從上到下關係)。 簡單的說,OOP是將 多個相似的對象的屬性與行爲封裝在一塊兒,並對其進行操做。而AOP是將 不一樣的對象的 相同行爲 封裝在一塊兒,並進行操做(能夠理解爲從左到右的關係)。
好比:日誌文件,使用OOP時,在不一樣的對象中可能須要進行一樣的日誌打印操做,這些代碼大量重複出現,代碼冗餘且重用率低。而使用AOP後,其能夠將這些相同的日誌打印操做抽取出來,並進行一系列操做,最終下降代碼冗餘。html
(1)橫切(cross-cutting)代碼:
散佈在各個類中,但與核心代碼無關的代碼。能夠理解爲 與業務代碼無關,可是被常用到的代碼。好比打印日誌。java
(2)核心關注點:
能夠理解爲 主要的業務流程。web
(3)橫切關注點:
能夠理解爲 與業務流程無關,但又出如今業務流程中的流程。好比:日誌、權限認證、事務處理等。spring
(4)切面(Aspect):
一種模塊化機制,橫切關注點的模塊化,能夠理解爲 將各個類中的公共行爲封裝到一個可重用模塊。express
(5)鏈接點(Join point):
鏈接點指的是在程序運行過程當中,可以插入切面的一個點,這個點能夠是類的某個方法調用前、調用後、方法拋出異常後等。切面代碼能夠利用這些點插入到應用的正常流程之中,並添加行爲。編程
(6)通知/加強(Advice):
在特定的鏈接點,AOP框架執行的動做。
分類:
前置通知(Before):在目標方法被調用以前調用通知功能。
後置通知(After):在目標方法完成以後調用通知,不管該方法是否發生異常。
後置返回通知(After-returning):在目標方法成功執行以後調用通知。
後置異常通知(After-throwing):在目標方法拋出異常後調用通知。
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。app
(7)切點(Pointcut):
指的是一個通知將被引起的一系列鏈接點的集合。AOP 經過切點定位到特定的鏈接點。切點和鏈接點不是一對一的關係,一個切點匹配多個鏈接點,切點經過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法做爲鏈接點的查詢條件。每一個類均可以擁有多個鏈接點。框架
(8)引入(Introduction):
添加方法或字段到被通知的類。 Spring容許引入新的接口到任何被通知的對象。ide
(9)目標對象(Target Object):
包含鏈接點的對象。也被稱做被通知或被代理對象。模塊化
(10)AOP代理(AOP Proxy):
AOP框架建立的對象,包含通知。 在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。
(11)織入(Weaving):
織入描述的是把切面應用到目標對象來建立新的代理對象的過程。 Spring AOP 的切面是在運行時被織入,原理是使用了動態代理技術。Spring支持兩種方式生成代理對象:JDK動態代理和CGLib,默認的策略是若是目標類是接口,則使用JDK動態代理技術(只對實現接口的類產生代理對象),不然使用Cglib來生成代理。
現有功能:一個簡單的計算器,能進行簡單的加減乘除。
【實例一:】 【Calculator.java】 package com.company; /** * 計算器接口,定義經常使用方法 */ public interface Calculator { /** * 加法操做 * @param a 實數 * @param b 實數 * @return 相加結果 */ public double add(double a, double b); /** * 減法操做 * @param a 實數 * @param b 實數 * @return 相減結果 */ public double sub(double a, double b); /** * 乘法操做 * @param a 實數 * @param b 實數 * @return 相乘結果 */ public double mul(double a, double b); /** * 除法操做 * @param a 實數 * @param b 實數 * @return 相除結果 */ public double div(double a, double b); } 【CalculatorImpl.java】 package com.company; /** * 計算器實現類,實現經常使用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public double div(double a, double b) { if(b == 0){ System.out.println("除數不能爲0"); return 0; } return a / b; } } 【Main.java】 package com.company; /** * 測試類 */ public class Main { public static void main(String[] args) { Calculator calculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); // a + b = 5.0 System.out.println("a - b = " + calculator.sub(a, b)); // a - b = 1.0 System.out.println("a * b = " + calculator.mul(a, b)); // a * b = 6.0 System.out.println("a / b = " + calculator.div(a, b)); // a / b = 1.5 } } }
需增長的功能:每次加減乘除的先後,都須要進行打印日誌操做。
(1)思路:在加減乘除方法的先後,能夠增長一個方法調用,進行日誌輸出。
(2)實現:(在實例一的基礎上修改)
【實例二:】 【CalculatorImpl.java】 package com.company; /** * 計算器實現類,實現經常使用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { beforeMethod("add", a, b); double c = a + b; afterMethod("add", c); return c; } @Override public double sub(double a, double b) { beforeMethod("sub", a, b); double c = a - b; afterMethod("sub", c); return c; } @Override public double mul(double a, double b) { beforeMethod("mul", a, b); double c = a * b; afterMethod("mul", c); return c; } @Override public double div(double a, double b) { beforeMethod("div", a, b); if (b == 0) { System.out.println("除數不能爲0"); double c = 0; afterMethod("div", c); return 0; } double c = a / b; afterMethod("div", c); return c; } /** * 在業務方法前執行打印日誌操做 * * @param methodName 方法名 */ public void beforeMethod(String methodName, double a, double b) { System.out.println("This method " + methodName + " begins with " + a + " , " + b); } /** * 在業務方法前執行後打印日誌操做 * * @param methodName 方法名 */ public void afterMethod(String methodName, double c) { System.out.println("This method " + methodName + " ends with " + c); } } 【執行結果:】 This method add begins with 3.0 , 2.0 This method add ends with 5.0 a + b = 5.0 This method sub begins with 3.0 , 2.0 This method sub ends with 1.0 a - b = 1.0 This method mul begins with 3.0 , 2.0 This method mul ends with 6.0 a * b = 6.0 This method div begins with 3.0 , 2.0 This method div ends with 1.5 a / b = 1.5
(3)優缺點分析:
代碼直觀,可是冗餘嚴重,當修改某個部分時,可能會形成大量修改。
(1)實例二可能會暴露的問題:對於不一樣的類,可能須要使用相同的日誌操做,如果使用實例二的寫法,那麼在每一個類裏面都需定義日誌操做方法,形成代碼冗餘。
解決: 在實例二的基礎上,將日誌操做抽取出來,造成一個類。當須要使用日誌時,繼承該類便可。
(2)實現:(在實例二的基礎上修改)
【實例三:】 【Message.java】 package com.company; public class Message { /** * 在業務方法前執行打印日誌操做 * * @param methodName 方法名 */ public void beforeMethod(String methodName, double a, double b) { System.out.println("This method " + methodName + " begins with " + a + " , " + b); } /** * 在業務方法前執行後打印日誌操做 * * @param methodName 方法名 */ public void afterMethod(String methodName, double c) { System.out.println("This method " + methodName + " ends with " + c); } } 【CalculatorImpl.java】 package com.company; /** * 計算器實現類,實現經常使用方法 */ public class CalculatorImpl extends Message implements Calculator { @Override public double add(double a, double b) { beforeMethod("add", a, b); double c = a + b; afterMethod("add", c); return c; } @Override public double sub(double a, double b) { beforeMethod("sub", a, b); double c = a - b; afterMethod("sub", c); return c; } @Override public double mul(double a, double b) { beforeMethod("mul", a, b); double c = a * b; afterMethod("mul", c); return c; } @Override public double div(double a, double b) { beforeMethod("div", a, b); if (b == 0) { System.out.println("除數不能爲0"); double c = 0; afterMethod("div", c); return c; } double c = a / b; afterMethod("div", c); return c; } } 【結果:】 This method add begins with 3.0 , 2.0 This method add ends with 5.0 a + b = 5.0 This method sub begins with 3.0 , 2.0 This method sub ends with 1.0 a - b = 1.0 This method mul begins with 3.0 , 2.0 This method mul ends with 6.0 a * b = 6.0 This method div begins with 3.0 , 2.0 This method div ends with 1.5 a / b = 1.5
(3)優缺點:
採用縱向繼承(從上到下),能夠減小代碼冗餘,可是侷限性太大。
(1)AOP思想採用橫向抽取的方式(從左到右),將日誌操做封裝。實質是經過動態代理來實現的。
(2)動態代理分類:
JDK動態代理:利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
Cglib動態代理:利用第三方jar包,將代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理。
(3)動態代理區別:
JDK動態代理:只能對實現了接口的類產生代理。
Cglib動態代理:對沒有實現接口的類產生代理對象,即針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法。
(4)JDK動態代理:
能夠參考:https://www.cnblogs.com/l-y-h/p/11111539.html#_label2
【實例四:】 核心類、接口: Calculator.java 接口 CalculatorImpl.java 接口實現類 CalculatorProxy.java 接口實現類的代理類 Main.java 測試類 【Calculator.java】 package com.company; /** * 計算器接口,定義經常使用方法 */ public interface Calculator { /** * 加法操做 * @param a 實數 * @param b 實數 * @return 相加結果 */ public double add(double a, double b); /** * 減法操做 * @param a 實數 * @param b 實數 * @return 相減結果 */ public double sub(double a, double b); /** * 乘法操做 * @param a 實數 * @param b 實數 * @return 相乘結果 */ public double mul(double a, double b); /** * 除法操做 * @param a 實數 * @param b 實數 * @return 相除結果 */ public double div(double a, double b); } 【CalculatorImpl.java】 package com.company; /** * 計算器實現類,實現經常使用方法 */ public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public double div(double a, double b) { if (b == 0) { System.out.println("除數不能爲0"); return 0; } return a / b; } } 【CalculatorProxy.java】 package com.company; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * JDK動態代理 */ public class CalculatorProxy implements InvocationHandler { private Object calculator; // 用於保存代理對象 CalculatorProxy(Object calculator) { this.calculator = calculator; // 獲取代理的對象 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // 獲取方法名 System.out.println("This is " + methodName + " starts with " + Arrays.asList(args)); Object obj = method.invoke(this.calculator, args); System.out.println("This is " + methodName + " ends with " + obj); return obj; } /** * 獲取動態代理的對象 * * @return 動態代理的對象 */ public Object getJDKProxy() { return Proxy.newProxyInstance(this.calculator.getClass().getClassLoader(), this.calculator.getClass().getInterfaces(), this); } } 【Main.java】 package com.company; import java.lang.reflect.Proxy; /** * 測試類 */ public class Main { public static void main(String[] args) { Calculator realCalculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; // 方式一 CalculatorProxy calculatorProxy = new CalculatorProxy(realCalculator); Calculator calculator = (Calculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), realCalculator.getClass().getInterfaces(), calculatorProxy); System.out.println("a + b = " + calculator.add(a, b)); System.out.println("a - b = " + calculator.sub(a, b)); System.out.println("a * b = " + calculator.mul(a, b)); System.out.println("a / b = " + calculator.div(a, b)); System.out.println(); System.out.println(); System.out.println(); System.out.println(); System.out.println(); // 方式二 Calculator calculator2 = (Calculator) (new CalculatorProxy(realCalculator).getJDKProxy()); System.out.println("a + b = " + calculator2.add(a, b)); System.out.println("a - b = " + calculator2.sub(a, b)); System.out.println("a * b = " + calculator2.mul(a, b)); System.out.println("a / b = " + calculator2.div(a, b)); } } 【結果:】 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5
(5)Cglib動態代理
要使用cglib,須要引入jar包(好比:cglib-2.2.2.jar, asm-3.3.1.jar)。
引入jar包不對的話,可能會引發下面的問題:
Question1:java.lang.NoClassDefFoundError: org/objectweb/asm/Type
Answer1:只引入了 cglib-2.2.2.jar, 未引入 asm-3.3.1.jar
Question2:class net.sf.cglib.core.DebuggingClassWriter overrides final method visit.
Answer2:引入了 asm-3.3.1.jar, 可是 版本不對,引起衝突。
【實例五:】 核心類: CalculatorImpl.java 某個類 CalculatorCglibProxy.java 某個類的代理類 Main.java 測試類 【CalculatorImpl.java】 package com.company; /** * 計算器類,定義經常使用方法 */ public class CalculatorImpl { public double add(double a, double b) { return a + b; } public double sub(double a, double b) { return a - b; } public double mul(double a, double b) { return a * b; } public double div(double a, double b) { if (b == 0) { System.out.println("除數不能爲0"); return 0; } return a / b; } } 【CalculatorCglibProxy.java】 package com.company; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Arrays; /** * Cglib實現動態代理 */ public class CalculatorCglibProxy implements MethodInterceptor{ private Object calculator; // 用於保存代理對象 CalculatorCglibProxy(Object calculator) { this.calculator = calculator; // 獲取代理的對象 } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String methodName = method.getName(); // 獲取方法名 System.out.println("This is " + methodName + " starts with " + Arrays.asList(objects)); Object obj = method.invoke(this.calculator, objects); System.out.println("This is " + methodName + " ends with " + obj); return obj; } /** * 獲取Cglib動態代理的對象 * * @return 動態代理的對象 */ public Object getCglibProxy(){ // 1.建立cglib的核心類對象 Enhancer enhancer = new Enhancer(); // 2.設置父類 enhancer.setSuperclass(calculator.getClass()); // 3.設置回調(相似於jdk動態代理中的InvocationHandler對象) enhancer.setCallback(this); // 4.返回代理對象 return enhancer.create(); } } 【Main.java】 package com.company; import net.sf.cglib.proxy.Enhancer; import java.lang.reflect.Proxy; /** * 測試類 */ public class Main { public static void main(String[] args) { CalculatorImpl realCalculator = new CalculatorImpl(); double a = 3.0; double b = 2.0; // 方式一 CalculatorCglibProxy calculatorCglibProxy = new CalculatorCglibProxy(realCalculator); CalculatorImpl calculator = (CalculatorImpl) new Enhancer().create(realCalculator.getClass(), calculatorCglibProxy); System.out.println("a + b = " + calculator.add(a, b)); System.out.println("a - b = " + calculator.sub(a, b)); System.out.println("a * b = " + calculator.mul(a, b)); System.out.println("a / b = " + calculator.div(a, b)); System.out.println(); System.out.println(); System.out.println(); System.out.println(); System.out.println(); // 方式二 CalculatorImpl calculator2 = (CalculatorImpl) (new CalculatorCglibProxy(realCalculator).getCglibProxy()); System.out.println("a + b = " + calculator2.add(a, b)); System.out.println("a - b = " + calculator2.sub(a, b)); System.out.println("a * b = " + calculator2.mul(a, b)); System.out.println("a / b = " + calculator2.div(a, b)); } } 【結果:】 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5 This is add starts with [3.0, 2.0] This is add ends with 5.0 a + b = 5.0 This is sub starts with [3.0, 2.0] This is sub ends with 1.0 a - b = 1.0 This is mul starts with [3.0, 2.0] This is mul ends with 6.0 a * b = 6.0 This is div starts with [3.0, 2.0] This is div ends with 1.5 a / b = 1.5
(1)切入點表達式的寫法: (基於execution的函數完成的)
execution([訪問修飾符] 返回值類型 包路徑.類名.方法名(參數)) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern (param-pattern) throws-pattern?) 其中:(?表示爲可選項) modifiers-pattern? 修飾符匹配 ret-type-pattern 返回值類型 declaring-type-pattern? 類路徑匹配 name-pattern 方法名匹配 (param-pattern) 參數匹配 throws-pattern? 異常類型匹配 「*」 表示任意返回值類型 「..」 表示任意參數 【舉例:】 全匹配方式: execution(public void com.company.CalculatorImpl.add(..)) 訪問修飾符可省略 execution(void com.company.CalculatorImpl.add(..)) 可以使用*,表示任意返回值 execution(* com.company.CalculatorImpl.add(..)) 包路徑可使用*,表示任意包. 可是*.的個數要和包的層級數相匹配,可使用*..,表示當前包,及其子包 execution(* *.*.CalculatorImpl.add(..)) 類名可使用*,表示任意類 execution(* *..*.add(..)) 方法名可使用*,表示任意方法 execution(* *..*.*(..)) 參數列表可使用*,表示參數能夠是任意數據類型,可是必須存在參數 execution(* *..*.*(*)) 參數列表可使用..表示有無參數都可,有參數能夠是任意類型 execution(* *..*.*(..)) 全通配方式,能夠匹配匹配任意方法 execution(* *..*.*(..))
(1)配置 通知類(切面) 與 被通知的類(目標對象)。
<!-- 配置目標對象,即被加強的對象 --> <bean id="calculator" class="com.company.CalculatorImpl"/> <!-- 將加強類(切面類)交給Spring管理 --> <bean id="calculatorEnhancer" class="com.company.CalculatorEnhancer"/>
(2)<aop:config>
用於聲明AOP配置,全部的AOP配置代碼均寫在其中。
<aop:config> <!-- AOP配置的代碼都寫在此處 --> </aop:config>
(3)<aop:aspect>
用於配置切面。
其屬性:
id : 爲指定切面的id,
ref :爲引用通知類的 id。
<aop:config> <!-- AOP配置的代碼都寫在此處 --> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置通知的類型要寫在此處--> </aop:aspect> </aop:config>
(4)<aop:pointcut>
用於配置切點表達式,用於指定對哪些方法進行加強。
其屬性:
id:爲指定切點表達式的id。
expression: 指定切入點表達式。
<aop:config> <!-- AOP配置的代碼都寫在此處 --> <!--配置切點表達式--> <aop:pointcut id="aspect1" expression="execution(* com.company.CalculatorImpl.*(..))"></aop:pointcut> <!--配置切面,通知的類型要寫在此處--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> </aop:aspect> </aop:config>
(5)配置通知方法
<aop:before>: 配置前置通知,指定的加強方法在切入點方法以前執行。
<aop:after-returning>: 配置後置返回通知,指定的加強方法在切入點方法正常執行以後執行。
<aop:after-throwing>: 配置後置異常通知,指定的加強方法在切入點方法產生異常後執行。
<aop:after>: 配置後置通知,不管切入點方法執行時是否發生異常,指定的加強方法都會最後執行。
<aop:around>: 配置環繞通知,能夠在代碼中手動控制加強代碼的執行時機。
其屬性:
method:用於指定 通知類中的 通知方法名。
ponitcut-ref:指定切入點的表達式的id。
poinitcut:指定切入點表達式。
<aop:config> <!-- AOP配置的代碼都寫在此處 --> <!--配置切點表達式--> <aop:pointcut id="aspect1" expression="execution(* com.company.CalculatorImpl.*(..))"></aop:pointcut> <!--配置通知的類型要寫在此處--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置各類類型的通知--> <aop:before method="printLogBefore" pointcut-ref="aspect1"></aop:before> <aop:after-returning method="printLogAfterReturning" pointcut-ref="aspect1"></aop:after-returning> <aop:after-throwing method="printLogAfterThrowing" pointcut-ref="aspect1"></aop:after-throwing> <aop:after method="printLogAfter" pointcut-ref="aspect1"></aop:after> <!--環繞通知通常單獨使用--> <!-- <aop:around method="printLogAround" pointcut-ref="aspect1"></aop:around> --> </aop:aspect> </aop:config>
(1)導入junit、aop、Spring等相關jar包。
注:若使用 junit 報錯: java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing 。
能夠導入hamcrest-core-1.3.jar。
(2)目錄結構
(3)代碼
注:環繞通知的返回值必須是Object,形參必須是ProceedingJoingPoint。
【實例六:】 核心類、接口: Calculator.java 接口 CalculatorImpl.java 接口實現類,被通知類 CalculatorEnhancer.java 通知(切面)類,用於定義相關通知方法 applicationContext.xml 配置文件,用於 通知類 與 被通知類 關聯 CalculatorTest.java 使用Junit進行單元測試 【Calculator.java】 package com.lyh.service; /** * 計算器接口,定義經常使用方法 */ public interface Calculator { /** * 加法操做 * @param a 實數 * @param b 實數 * @return 相加結果 */ public double add(double a, double b); /** * 減法操做 * @param a 實數 * @param b 實數 * @return 相減結果 */ public double sub(double a, double b); /** * 乘法操做 * @param a 實數 * @param b 實數 * @return 相乘結果 */ public double mul(double a, double b); /** * 除法操做 * @param a 實數 * @param b 實數 * @return 相除結果 */ public int div(int a, int b); } 【CalculatorImpl.java】 package com.lyh.service.impl; import com.lyh.service.Calculator; /** * 計算器實現類,實現經常使用方法 */ public class CalculatorImpl implements Calculator{ @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public int div(int a, int b) { return a / b; } } 【CalculatorEnhancer.java】 package com.lyh.enhancer; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Arrays; /** * 定義一個切面類 */ public class CalculatorEnhancer { /** * 前置通知:在方法執行前執行的代碼 * @param joinPoint 切入點 */ public void beforeExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("beforeExecute"); System.out.println("This is " + methodName + " starts with " + Arrays.asList(joinPoint.getArgs())); System.out.println(); } /** * 後置通知:在方法執行後執行的代碼(不管該方法是否發生異常),注意後置通知拿不到執行的結果 * @param joinPoint 切入點 */ public void afterExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExecute"); System.out.println(); } /** * 後置返回通知:在方法正常執行後執行的代碼,能夠獲取到方法的返回值 * @param joinPoint 切入點 * @param result 方法執行的結果 */ public void afterReturningExecute(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterReturningExecute"); System.out.println("This is " + methodName + " ends with " + result); System.out.println(); } /** * 後置異常通知:在方法拋出異常以後執行,能夠訪問到異常信息。 * @param joinPoint 切入點 * @param exception 異常信息 */ public void afterExceptionExecute(JoinPoint joinPoint, Exception exception){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExceptionExecute"); System.out.println("This is " + methodName + " exception with: " + exception); System.out.println(); } /** * 環繞通知。 * Spring框架爲咱們提供一個接口ProceedingJoinPoint,它的實例對象能夠做爲環繞通知方法的參數,經過參數控制被加強方法的執行時機。 * ProceedingJoinPoint對象的getArgs()方法返回被攔截的參數 * ProceedingJoinPoint對象的proceed()方法執行被攔截的方法 * @param joinPoint 鏈接點 * @return 方法計算的結果值 */ public Object aroundExecute(ProceedingJoinPoint joinPoint){ Object result = null; try{ System.out.println("beforeExecute"); result = joinPoint.proceed(joinPoint.getArgs()); System.out.println("afterReturningExecute"); }catch (Throwable e){ System.out.println("afterExceptionExecute"); }finally { System.out.println("afterExecute"); } return result; } } 【applicationContext.xml】 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置目標對象,即被加強的對象 --> <bean id="calculator" class="com.lyh.service.impl.CalculatorImpl"/> <!-- 將加強類(切面類)交給Spring管理 --> <bean id="calculatorEnhancer" class="com.lyh.enhancer.CalculatorEnhancer"/> <aop:config> <!-- AOP配置的代碼都寫在此處 --> <!--配置切點表達式--> <aop:pointcut id="beforeTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterReturningTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.add(..))"></aop:pointcut> <aop:pointcut id="afterThrowingTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.div(..))"></aop:pointcut> <aop:pointcut id="aroundTest" expression="execution(* com.lyh.service.impl.CalculatorImpl.sub(..))"></aop:pointcut> <!--配置通知的類型要寫在此處--> <aop:aspect id="calculatorAdvice" ref="calculatorEnhancer"> <!--配置各類類型的通知--> <aop:before method="beforeExecute" pointcut-ref="beforeTest"></aop:before> <aop:after method="afterExecute" pointcut-ref="afterTest"></aop:after> <aop:after-returning method="afterReturningExecute" pointcut-ref="afterReturningTest" returning="result"></aop:after-returning> <aop:after-throwing method="afterExceptionExecute" pointcut-ref="afterThrowingTest" throwing="exception"></aop:after-throwing> <aop:around method="aroundExecute" pointcut-ref="aroundTest"></aop:around> </aop:aspect> </aop:config> </beans> 【CalculatorTest.java】 package com.lyh.test; import com.lyh.service.Calculator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.math.BigDecimal; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class CalculatorTest { @Autowired private Calculator calculator; /** * 測試 前置通知,後置通知,後置返回通知 */ @Test public void test(){ double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); } /** * 測試後置異常通知, * 對於除數爲0的狀況(即b = 0時), * 若是兩者均爲int型(long也是int型),結果會拋出異常:java.lang.ArithmeticException: / by zero。 * 若是其中有一個爲double或者float型,結果則是Infinity。 * 爲了測試異常,此處將數據置爲int型的。 */ @Test public void test2(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.div(a, b)); } /** * 測試環繞通知 */ @Test public void test3(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.sub(a, b)); } } 【測試結果:】 【運行test()】 beforeExecute This is add starts with [3.0, 2.0] afterReturningExecute This is add ends with 5.0 afterExecute a + b = 5.0 【運行test1()】 afterExceptionExecute This is div exception with: java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at com.lyh.service.impl.CalculatorImpl.div(CalculatorImpl.java:29) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy13.div(Unknown Source) at com.lyh.test.CalculatorTest.test2(CalculatorTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) 【運行test3()】 beforeExecute afterReturningExecute afterExecute a / b = 3.0
注:
使用xml版的時候,執行的順序 按照 配置文件中的 順序執行。
可能會出現 前置通知 -》 後置通知(最終通知) -》 後置返回通知 的狀況。
也可能會出現 前置通知 -》 後置返回通知 -》 後置通知(最終通知) 的狀況。
(1)在上例 xml 版的基礎上,在 applicationContext.xml 中 添加 <aop:aspectj-autoproxy /> 用於自動代理,同時在相應的地方添加上註解標記。
(2)代碼
注: 使用 @Aspect 後執行順序是 前置通知,後置通知,後置返回通知,能夠用 環繞通知 來解決。
【實例七:】 核心類、接口: Calculator.java 接口 CalculatorImpl.java 接口實現類,被通知類 CalculatorEnhancer.java 通知(切面)類,用於定義相關通知方法 applicationContext.xml 配置文件,用於 通知類 與 被通知類 關聯 CalculatorTest.java 使用Junit進行單元測試 【Calculator.java】 package com.lyh.service; /** * 計算器接口,定義經常使用方法 */ public interface Calculator { /** * 加法操做 * @param a 實數 * @param b 實數 * @return 相加結果 */ public double add(double a, double b); /** * 減法操做 * @param a 實數 * @param b 實數 * @return 相減結果 */ public double sub(double a, double b); /** * 乘法操做 * @param a 實數 * @param b 實數 * @return 相乘結果 */ public double mul(double a, double b); /** * 除法操做 * @param a 實數 * @param b 實數 * @return 相除結果 */ public int div(int a, int b); } 【CalculatorImpl.java】 package com.lyh.service.impl; import com.lyh.service.Calculator; import org.springframework.stereotype.Service; import javax.xml.ws.ServiceMode; /** * 計算器實現類,實現經常使用方法 */ @Service public class CalculatorImpl implements Calculator{ @Override public double add(double a, double b) { return a + b; } @Override public double sub(double a, double b) { return a - b; } @Override public double mul(double a, double b) { return a * b; } @Override public int div(int a, int b) { return a / b; } } 【CalculatorEnhancer.java】 package com.lyh.enhancer; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 定義一個切面類 */ @Component @Aspect public class CalculatorEnhancer { /** * 前置通知:在方法執行前執行的代碼 * @param joinPoint 切入點 */ @Before("execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void beforeExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("beforeExecute"); System.out.println("This is " + methodName + " starts with " + Arrays.asList(joinPoint.getArgs())); System.out.println(); } /** * 後置返回通知:在方法正常執行後執行的代碼,能夠獲取到方法的返回值 * @param joinPoint 切入點 * @param result 方法執行的結果 */ @AfterReturning(returning = "result", value = "execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void afterReturningExecute(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterReturningExecute"); System.out.println("This is " + methodName + " ends with " + result); System.out.println(); } /** * 後置通知:在方法執行後執行的代碼(不管該方法是否發生異常),注意後置通知拿不到執行的結果 * @param joinPoint 切入點 */ @After("execution(* com.lyh.service.impl.CalculatorImpl.add(..))") public void afterExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExecute"); System.out.println(); } /** * 後置異常通知:在方法拋出異常以後執行,能夠訪問到異常信息。 * @param joinPoint 切入點 * @param exception 異常信息 */ @AfterThrowing(throwing ="exception", value = "execution(* com.lyh.service.impl.CalculatorImpl.div(..))") public void afterExceptionExecute(JoinPoint joinPoint, Exception exception){ String methodName = joinPoint.getSignature().getName(); System.out.println("afterExceptionExecute"); System.out.println("This is " + methodName + " exception with: " + exception); System.out.println(); } /** * 環繞通知。 * Spring框架爲咱們提供一個接口ProceedingJoinPoint,它的實例對象能夠做爲環繞通知方法的參數,經過參數控制被加強方法的執行時機。 * ProceedingJoinPoint對象的getArgs()方法返回被攔截的參數 * ProceedingJoinPoint對象的proceed()方法執行被攔截的方法 * @param joinPoint 鏈接點 * @return 方法計算的結果值 */ @Around("execution(* com.lyh.service.impl.CalculatorImpl.sub(..))") public Object aroundExecute(ProceedingJoinPoint joinPoint){ Object result = null; try{ System.out.println("beforeExecute"); result = joinPoint.proceed(joinPoint.getArgs()); System.out.println("afterReturningExecute"); }catch (Throwable e){ System.out.println("afterExceptionExecute"); }finally { System.out.println("afterExecute"); } return result; } } 【applicationContext.xml 】 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 自動掃描的包 --> <context:component-scan base-package="com.lyh"></context:component-scan> <!-- 啓用AspectJ自動代理 --> <aop:aspectj-autoproxy /> </beans> 【CalculatorTest.java】 package com.lyh.test; import com.lyh.service.Calculator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.math.BigDecimal; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class CalculatorTest { @Autowired private Calculator calculator; /** * 測試 前置通知,後置通知,後置返回通知 */ @Test public void test(){ double a = 3.0; double b = 2.0; System.out.println("a + b = " + calculator.add(a, b)); } /** * 測試後置異常通知, * 對於除數爲0的狀況(即b = 0時), * 若是兩者均爲int型(long也是int型),結果會拋出異常:java.lang.ArithmeticException: / by zero。 * 若是其中有一個爲double或者float型,結果則是Infinity。 * 爲了測試異常,此處將數據置爲int型的。 */ @Test public void test2(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.div(a, b)); } /** * 測試環繞通知 */ @Test public void test3(){ int a = 3; int b = 0; System.out.println("a / b = " + calculator.sub(a, b)); } } 【測試結果:】 【運行test()】 這裏能夠看到這裏的執行順序是 前置通知,後置通知,後置返回通知 beforeExecute This is add starts with [3.0, 2.0] afterExecute afterReturningExecute This is add ends with 5.0 a + b = 5.0 【運行test2()】 afterExceptionExecute This is div exception with: java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at com.lyh.service.impl.CalculatorImpl.div(CalculatorImpl.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy20.div(Unknown Source) at com.lyh.test.CalculatorTest.test2(CalculatorTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) 【運行test3()】 beforeExecute afterReturningExecute afterExecute a / b = 3.0