Spring-AOP切面程式設計總結
寫在前面:
之前寫了三篇JAVA基礎進階、一篇JAVA原始碼解析,今天又過來寫框架,大家別擔心,另外兩個以後還會繼續寫的,給大家預告下,下一篇部落格會寫JAVA基礎進階的《JAVA反射機制》,然後會寫JAVA原始碼解析的集合原始碼解析,那塊應該一兩個集合型別就是一篇部落格了吧。近期打算把部落格搬到github上面去了,但是這裡以後還會繼續更新的,兩邊都一樣。
.1 AOP簡介
AOP是面向切面程式設計(Aspect-Oriented Programming)的縮寫,是對傳統的面向物件程式設計的一種補充,下面來介紹下在AOP中經常用到的一些術語。
切面(Aspect):橫切關注點(跨越應用程式多個模組的功能)被模組化的特殊物件
通知(Advice):切面必須要完成的工作
目標(Target):被通知的物件
代理(Proxy): 向目標物件應用通知之後建立的物件
連線點(Joinpoint):程式執行的某個特定位置:如類某個方法呼叫前、呼叫後、方法丟擲異常後等。
切點(pointcut):一個類中可以有多個連線點,就和每個類中可以有多個方法一樣,AOP通過切點定位到特定的連線點。可以這麼理解:連線點相當於資料庫中的記錄,切點則相當於查詢條件,切點使用類和方法作為連線點的查詢條件。
AOP切面程式設計是圍繞著通知註解這個東西展開的,在SpringAOP中共有五種型別的通知註解:
@Before: 前置通知,在方法執行之前執行
@After: 後置通知,在方法執行之後執行
@AfterRunning:返回通知,在方法返回結果之後執行
@AfterThrowing:異常通知,在方法丟擲異常之後
@Around:環繞通知,圍繞著方法執行 (並不常用)
.2 一個小需求
我通過一個小小的需求來向大家展示SpringAOP程式設計的各個細節,比如說,我們寫了兩個方法:一個方法將兩個整形的數字做加法運算、一個方法將兩個整形數字做除法運算,這樣一個需求看起來非常簡單。但是,我們要求,在方法開始時、結束時、返回時、發生異常時,分別在控制檯上輸出一段文字來說明當前的狀態,這時該怎麼辦呢??
我們通過AOP可以很輕鬆的解決這個問題。
.3 通過註解的方式配置AOP
首先建立一個JAVA專案,在專案下匯入Spring的開發包,然後建立一個介面ArithmeticCalculator,在介面中定義兩個方法,add()和div()。程式碼如下:
package com.byh.aop.hello;
public interface ArithmeticCalculator {
int add(int i,int j);
int div(int i,int j);
}
在src下建立Spring的配置檔案applicationContext.xml,並在Namespaces選項中勾選aop和context這兩個選項,之後在applicationContext.xml中啟用 AspectJ 註解支援,並指定IOC容器的掃描範圍。程式碼如下:
<context:component-scan base-package="com.byh.aop.hello"></context:component-scan> <!-- 指定IOC容器的掃描範圍 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 啟用 AspectJ 註解支援 -->
新增該介面的實現類ArithmeticCalculatorImpl,實現ArithmeticCalculator介面的兩個方法。程式碼如下:
package com.byh.aop.hello;
import org.springframework.stereotype.Component;
@Component //將該類注入到IOC容器中
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i+j;
return result;
}
@Override
public int div(int i, int j) {
int result = i/j;
return result;
}
}
寫一個測試類來測試,程式碼如下:
package com.byh.aop.hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
int result = arithmeticCalculator.add(3, 6);
System.out.println("result:"+result);
result = arithmeticCalculator.div(12, 6);
System.out.println("result:"+result);
}
}
執行之後我們發現執行是成功的,但是這還沒有達到我們的要求,在需求中我們要求在方法開始時、結束時、返回時、發生異常時,分別在控制檯上輸出一段文字來說明當前的狀態,現在我們就用AOP來實現。
.3-1 前置通知
首先我們來想如何讓方法開始時在控制檯輸出一段話呢?這是就要用到AOP的前置通知了。前置通知就是在方法執行之前執行的通知,前置通知使用@Before註解,並將切入點表示式的值也就是該方法的路徑,作為其註解值。如果要把一個類宣告為一個切面的話,只要在該類的前面加上@Aspect註解就可以了。可以在通知方法中宣告一個型別為 JoinPoint 的引數.。然後就能訪問連結細節. 如方法名稱和引數值.。
我們來新建一個類名為LoggingAspect,並在該類中實現前置通知,程式碼如下:
@Component //將該類注入到IOC容器
@Aspect //將該類生命為切面
public class LoggingAspect {
@Before(value = "execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")//將該方法宣告為前置通知,切入點表示式的值為連線點的路徑
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("前置通知: The method "+methodName+" begins with "+args);
}
}
.3-2 後置通知
後置通知是在連線點完成之後執行的, 即連線點返回結果或者丟擲異常的時候,。一個切面可以包括一個或者多個通知,所以根據我們的需求,我們在剛才前置通知的基礎上在LoggingAspect類中新增實現後置通知的方法即可,程式碼如下:
@After(value = "execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("後置通知: end method "+methodName);
}
這時不知道大家有沒有發現一個問題,我們在每一個通知的前面都要寫上切點表示式,而且切點表示式的內容是一樣的,這樣就不符合我們編碼的風格了,該如何進行簡化呢?我們可以宣告一個類去指定切入點表示式的值,這個方法中什麼都不用寫,只需要在方法前使用@Pointcut註解來指定切點表示式即可,程式碼如下。
@Pointcut("execution(public int com.byh.aop.hello.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}
將該方法加入到LoggingAspect類中,並且寫在所有通知之前,這樣一來,當需要生命切入點表示式時,直接將該方法的名字寫上就好了,我們修改之前寫的前置通知和後置通知的切入點表示式宣告方法,程式碼如下:
@Before("declareJointPointExpression()")//修改切入點表示式的宣告方法
public void beforeMethod(JoinPoint joinPoint){
...
}
.3-3 返回通知
無論連線點是正常返回還是丟擲異常, 後置通知都會執行.。如果想在連線點返回的時候記錄日誌, 應使用返回通知。程式碼如下:
@AfterReturning(value="declareJointPointExpression()",
returning="result")//returning指定返回的引數
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知: The method "+methodName+" ends with "+result);
}
.3-4 異常通知
異常通知只在連線點丟擲異常時才會執行,將 throwing 屬性新增到 @AfterThrowing 註解中, 也可以訪問連線點丟擲的異常..Throwable 是所有錯誤和異常類的超類.。所以在異常通知方法可以捕獲到任何錯誤和異常。如果只對某種特殊的異常型別感興趣, 可以將引數宣告為其他異常的引數型別, 然後通知就只在丟擲這個型別及其子類的異常時才被執行。程式碼如下:
@AfterThrowing(value="declareJointPointExpression()",
throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("異常通知: The method "+methodName+" occurs exception: "+ex);
}
這時我們再去執行下測試方法,發現執行的結果已經變成了這樣:
前置通知: The method add begins with [3, 6]
後置通知: end method add
返回通知: The method add ends with 9
result:9
前置通知: The method div begins with [12, 6]
後置通知: end method div
返回通知: The method div ends with 2
result:2
這就表明我們寫的幾個通知已經起到它該有的作用了,測試異常通知的話,只需將除法的被除數改為0即可,這裡就不做演示了,至此我們已經瞭解AOP到底是如何使用的了。
.4 通過基於 XML 的配置來配置AOP
除了使用註解的方式去配置之外,我們還可以使用配置檔案的方式去配置。
在 Bean 配置檔案中, 所有的 Spring AOP 配置都必須定義在 aop:config 元素內部。
對於每個切面而言, 都要建立一個 aop:aspect 元素來為具體的切面實現引用後端 Bean 例項。
切入點使用 aop:pointcut 元素宣告,切入點必須定義在 aop:aspect 元素下, 或者直接定義在 aop:config 元素下。區別是定義在 aop:aspect 元素下: 只對當前切面有效,定義在 aop:config 元素下: 對所有切面都有效。
修改applicationContext.xml,程式碼如下:
<context:component-scan base-package="com.byh.aop.hello"></context:component-scan>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切點表示式 -->
<aop:pointcut expression="execution(* com.byh.aop.hello.ArithmeticCalculator.*(..))" id="pointcut"/>
<!--宣告切面-->
<aop:aspect ref="loggingAspect">
<!--前置通知-->
<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:config>
然後我們將LoggingAspect類中所有關於AOP的註解都刪除掉,刪除之後程式碼如下:
@Component
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("前置通知: The method "+methodName+" begins with "+args);
}
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("後置通知: end method "+methodName);
}
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知: The method "+methodName+" ends with "+result);
}
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("異常通知: The method "+methodName+" occurs exception: "+ex);
}
}
之後執行測試方法,發現執行結果與使用註解進行配置的方式是一樣的。
筆者水平有限,若有錯漏,歡迎指正,如果轉載以及CV操作,請務必註明出處,謝謝!