JavaWeb 筆記之 Spring AOP
阿新 • • 發佈:2018-12-14
Spring AOP
- AspectJ:Java 社群裡最完整最流行的 AOP 框架
- 在 Spring2.0 以上版本中, 可以使用基於 AspectJ 註解或基於 XML 配置的 AOP
基於 AspectJ 註解的 AOP
啟用 AspectJ 註解支援
- 引入 Jar 包(aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar、spring-aop.jar)
- 將 aop Schema 新增到 <beans> 根元素中
- 要在 Spring IOC 容器中啟用 AspectJ 註解支援, 只要在 Bean 配置檔案中定義一個空的 XML 元素 <aop:aspectj-autoproxy>
- 當 Spring IOC 容器偵測到 Bean 配置檔案中的 <aop:aspectj-autoproxy> 元素時, 會自動為與 AspectJ 切面匹配的 Bean 建立代理.
用 AspectJ 註解宣告切面
- 要在 Spring 中宣告 AspectJ 切面, 只需要在 IOC 容器中將切面宣告為 Bean 例項. 當在 Spring IOC 容器中初始化 AspectJ 切面之後, Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 建立代理.
- 在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類.
- 通知是標註有某種註解的簡單的 Java 方法.
- AspectJ 支援 5 種類型的通知註解:
- @Before: 前置通知, 在方法執行之前執行
- @After: 後置通知, 在方法執行之後執行
- @AfterRunning: 返回通知, 在方法返回結果之後執行
- @AfterThrowing: 異常通知, 在方法丟擲異常之後
- @Around: 環繞通知, 圍繞著方法執行
1. 前置通知
- 前置通知:在方法執行之前執行的通知
- 前置通知使用 @Before 註解, 並將切入點表示式的值作為註解值.
@Aspect @Component public class LoggingAspect { @Before("execution(public int com.axon.spring5.aop.impl.ArithmeticCalculator.add(int, int))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = (List<Object>) Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + "begins with "+args); } }
- 編寫 AspectJ 切入點表示式
- execution * com.axon.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中宣告的所有方法,第一個 * 代表任意修飾符及任意返回值. 第二個 * 代表任意方法. .. 匹配任意數量的引數. 若目標類與介面與該切面在同一個包中, 可以省略包名.
- execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 介面的 所有公有方法.
- execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中 返回 double 型別數值的方法
- execution public double ArithmeticCalculator.*(double, ..): 匹配第一個引數為 double 型別的方法, .. 匹配任意數量任意型別的引數
- execution public double ArithmeticCalculator.*(double, double): 匹配引數型別為 double, double 型別的方法.
- 在 AspectJ 中, 切入點表示式可以通過操作符 &&, ||, ! 結合起來
@Aspect @Component public class LoggingAspect { @Pointcut("execution(* *.add(int,..)) || execution(* *.sub(int,..))") private void loggingOperation(){} @Before("loggingOperation()") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = (List<Object>) Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + "begins with "+args); } }
2. 後置通知
- 後置通知是在連線點完成之後執行的, 即連線點返回結果或者丟擲異常的時候, 下面的後置通知記錄了方法的終止.
- 一個切面可以包括一個或者多個通知.
- 後置通知還不能訪問目標方法執行的結果
@After("execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); }
3. 返回通知
- 無論連線點是正常返回還是丟擲異常, 後置通知都會執行. 如果只想在連線點返回的時候記錄日誌, 應使用返回通知代替後置通知.
- 可以訪問方法的返回值
@AfterReturning(pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))",returning="result") public void afterReturning(JoinPoint joinPoint,Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with " + result); }
4. 異常通知
- 只在連線點丟擲異常時才執行異常通知
- 將 throwing 屬性新增到 @AfterThrowing 註解中, 也可以訪問連線點丟擲的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常.
- 如果只對某種特殊的異常型別感興趣, 可以將引數宣告為其他異常的引數型別. 然後通知就只在丟擲這個型別及其子類的異常時才被執行.
@AfterThrowing(value="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))",throwing="ex") public void afterThrowing(JoinPoint joinPoint,Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception: " + ex); }
5. 環繞通知
- 環繞通知是所有通知型別中功能最為強大的,環繞通知類似於動態代理的全過程
- ProceedingJoinPoint . 它是 JoinPoint 的子介面, 允許控制何時執行, 是否執行連線點.
- 注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即呼叫 joinPoint.proceed(); 的返回值, 否則會出現空指標異常
@Around("execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))") public Object aroundMethod(ProceedingJoinPoint pjd) throws Throwable { Object result = null; String methodName = pjd.getSignature().getName(); //執行目標方法 try{ //前置通知 System.out.println("The method " + methodName + " begins with "+ Arrays.asList(pjd.getArgs())); result = pjd.proceed(); //返回通知 System.out.println("The method " + methodName + " ends with " + result); } catch (Throwable e){ //異常通知 System.out.println("The method " + methodName + " occurs exception: " + e); throw new RuntimeException(e); } //後置通知 System.out.println("The method " + methodName + " ends "); return result; }
指定切面的優先順序
- 同一個連線點上應用不止一個切面時, 除非明確指定, 否則它們的優先順序是不確定的
- 切面的優先順序可以通過實現 Ordered 介面或利用 @Order 註解指定.
- 實現 Ordered 介面, getOrder() 方法的返回值越小, 優先順序越高.
- 若使用 @Order 註解, 序號出現在註解中
@Aspect @Order(1) @Component public class LoggingAspect { } @Aspect @Order(2) @Component public class ValidationAspect
基於 XML 的配置的 AOP
- 除了使用 AspectJ 註解宣告切面, Spring 也支援在 Bean 配置檔案中宣告切面. 這種宣告是通過 aop schema 中的 XML 元素完成的.
- 正常情況下, 基於註解的宣告要優先於基於 XML 的宣告. 通過 AspectJ 註解, 切面可以與 AspectJ 相容, 而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支援, 所以以註解風格編寫的切面將會有更多重用的機會.
宣告切面步驟
- 首先 需要在 <beans> 根元素中匯入 aop Schema
- 在 Bean 配置檔案中, 所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內部. 對於每個切面而言, 都要建立一個 <aop:aspect> 元素來為具體的切面實現引用後端 Bean 例項.
- 切面 Bean 必須有一個標示符, 供 <aop:aspect> 元素引用
- 切入點使用 <aop:pointcut> 元素宣告
- 切入點必須定義在 <aop:aspect> 元素下, 或者直接定義在 <aop:config> 元素下.
- 定義在 <aop:aspect> 元素下: 只對當前切面有效
<!-- 配置 aop --> <aop:config> <!-- 配置切面以及通知 --> <aop:aspect ref="xxxx" order="1"> <aop:around method="aroundMethod" pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" /> </aop:aspect> </aop:config>
- 定義在 <aop:config> 元素下: 對所有切面都有效
<aop:config> <!-- 配置切入點表示式 --> <aop:pointcut expression="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" id="pointcut" /> <aop:aspect ref="xxxx" order="1"> <aop:before method="beforeMethod" pointcut-ref="pointcut" /> <aop:after method="afterMethod" pointcut-ref="pointcut" /> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> <aop:aspect ref="xxxx" order="0"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
- 基於 XML 的 AOP 配置不允許在切入點表示式中用名稱引用其他切入點.
- 詳細示例:
<!-- 配置切面的 bean --> <bean id="loggingAspect" class="com.axon.spring5.aop.impl.LoggingAspect"></bean> <bean id="validationAspect" class="com.axon.spring5.aop.impl.ValidationAspect"></bean> <!-- 配置 aop --> <aop:config> <!-- 配置切入點表示式 --> <aop:pointcut expression="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" id="pointcut" /> <!-- 配置切面以及通知 --> <aop:aspect ref="loggingAspect" order="1"> <aop:before method="beforeMethod" pointcut-ref="pointcut" /> <aop:after method="afterMethod" pointcut-ref="pointcut" /> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> <!-- <aop:around method="aroundMethod" pointcut-ref="pointcut" /> --> <!-- <aop:around method="aroundMethod" pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" /> --> </aop:aspect> <aop:aspect ref="validationAspect" order="0"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>