一、AspectJ
1、介紹
AspectJ是一個面向切面的框架,它擴充套件了Java語言。AspectJ定義了AOP語法,也可以說 AspectJ 是一個基於 Java 語言的 AOP 框架。通常我們在使用 Spring AOP 的時候,都會匯入 AspectJ 的相關 jar 包。
2、案例(xml)
定義目標物件(被代理的物件)(與上一章相同)
編寫一個切面類(通知)
1 // 建立切面類(包含各種通知)
2 public class MyAspect {
3
4 // JoinPoint 能獲取目標方法的一些基本資訊
5 public void myBefore(JoinPoint joinPoint) {
6 System.out.println("前置通知:方法增強myBefore()" + " , -->" + joinPoint.getSignature().getName());
7 }
8
9 // object:目標方法的返回值
10 public void myAfterReturning(JoinPoint joinPoint, Object object) {
11 System.out.println("後置通知:方法增強myAfterReturning()" + " , -->" + joinPoint.getSignature().getName() + " , -->" + object);
12 }
13
14 public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
15 System.out.println("============環繞前==============");
16 Object obj = joinPoint.proceed(); // 手動執行目標方法
17 System.out.println("============環繞後==============");
18 return obj;
19 }
20
21 public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
22 System.out.println("丟擲異常通知:" + e.getMessage());
23 }
24
25 public void myAfter() {
26 System.out.println("最終通知:方法增強myAfter()");
27 }
28
29 }
編寫配置檔案 application.xml
1 <!-- 配置目標物件 -->
2 <bean id="teacher" class="com.lx.spring.common.Teacher"/>
3 <!-- 配置切面物件(通知) -->
4 <bean id="myAspect" class="com.lx.spring.day3.MyAspect"/>
5
6 <aop:config>
7 <!-- 切入點表示式,指明瞭在哪裡引入通知 -->
8 <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/>
9
10 <!-- 方法增強,指明瞭引入一個什麼樣的通知 -->
11 <aop:aspect ref="myAspect">
12 <aop:before method="myBefore" pointcut-ref="myPointcut"/>
13 <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/>
14 <aop:around method="myAround" pointcut-ref="myPointcut"/>
15 <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
16 <aop:after method="myAfter" pointcut-ref="myPointcut"/>
17 </aop:aspect>
18 </aop:config>
1 // 測試類
2 public class Main {
3 public static void main(String[] args) {
4 ApplicationContext app = new ClassPathXmlApplicationContext("app3.xml");
5 ITeacher iTeacher = app.getBean(ITeacher.class);
6
7 iTeacher.add(11, 24);
8 }
9 }
10
11 // 結果一:未配置環繞通知時.注意:此時 最終通知 在 後置通知後面
12 前置通知:方法增強myBefore() , -->add
13 執行目標方法:老師正在做加法,結果為:35
14 後置通知:方法增強myAfterReturning() , -->add , -->35
15 最終通知:方法增強myAfter()
16
17
18 // 結果二:配置環繞通知時.注意:此時 最終通知 在 後置通知前面
19 前置通知:方法增強myBefore() , -->add
20 ============環繞前==============
21 執行目標方法:老師正在做加法,結果為:35
22 最終通知:方法增強myAfter()
23 ============環繞後==============
24 後置通知:方法增強myAfterReturning() , -->add , -->35
說明:這裡有很多細節需要補充一下。深刻理解通知,重點思想在於:①在哪裡(切點,或者說方法)引入?②引入一個什麼樣的通知?針對這兩個問題,則不難理解AOP。
①切入點表示式:指明瞭在哪裡(切點,在哪個方法)引入一個通知(即對目標方法的增強),也就是在哪些方法進行增強。execution 是 AspectJ 框架定義的一個切入點函式,其語法形式如下:
1 execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
2 類修飾符 返回值 方法所在的包 方法名(引數) 方法丟擲的異常
那麼不難理解,對滿足以下規則的方法進行增強。也就是對這些方法引入一個通知。
1 <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/>
2 選擇方法 任意返回值 此包下.此介面 任意方法名(任意引數)
②通知(方法增強):指明瞭對滿足切點表示式的方法引入一個什麼樣的通知。
1 <!-- 指明引入的切面(通知) -->
2 <aop:aspect ref="myAspect">
3 <!-- 對滿足上面切入點表示式的方法配置一個前置通知 -->
4 <!-- 即在目標方法前執行方法 myBefore -->
5 <aop:before method="myBefore" pointcut-ref="myPointcut"/>
6
7 <!-- 對滿足上面切入點表示式的方法配置一個後置通知 -->
8 <!-- 即在目標方法後執行方法 myAfterReturning -->
9 <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/>
10 </aop:aspect>
3、切入點表示式
上一節中已經介紹過切入點表示式的相關語法,且理解不難。再補充幾點,如果切入點表示式有多個不同目錄呢?可以通過 || 來表示或的關係。
1 <!--表示匹配com.lx.aop包下的,以Service結尾或者以Facade結尾的類的任意方法。-->
2 <aop:pointcut id="myPointcut" expression="execution(* com.lx.aop.*Service.*(..)) || execution(* com.lx.aop.*Facade.*(..))"/>
AOP 切入點表示式支援多種形式的定義規則:
1 1、execution:匹配方法的執行(常用)
2 execution(public *.*(..))
3 2、within:匹配包或子包中的方法(瞭解)
4 within(com.ys.aop..*)
5 3、this:匹配實現介面的代理物件中的方法(瞭解)
6 this(com.ys.aop.user.UserDAO)
7 4、target:匹配實現介面的目標物件中的方法(瞭解)
8 target(com.ys.aop.user.UserDAO)
9 5、args:匹配引數格式符合標準的方法(瞭解)
10 args(int,int)
11 6、bean(id):對指定的bean所有的方法(瞭解)
12 bean('userServiceId')
4、通知型別
通知型別
|
介面
|
描述
|
前置通知
(before)
|
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
|
在目標方法前呼叫,如果通過丟擲異常,阻止方法執行。
應用:各種校驗。
|
後置通知
(afterReturning)
|
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
|
在目標方法後呼叫,可以獲得目標方法返回值,若目標方法丟擲異常,通知無法執行。
應用:常規資料處理。
|
環繞通知
(around)
|
org.springframework.aop.aspectj.AspectJAroundAdvice
|
在目標方法前後呼叫,可以阻止方法的執行,必須手動執行目標方法。
應用:十分強大,可以做任何事情。
|
異常通知
(afterThrowing)
|
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
|
目標方法丟擲異常時呼叫,若目標方法沒有丟擲異常,無法執行。
應用:包裝異常資訊
|
最終通知
(after)
|
org.springframework.aop.aspectj.AspectJAfterAdvice
|
目標方法執行完畢後執行,無論方法中是否出現異常
應用:清理現場
|
這裡最重要的是around,環繞通知,它可以代替上面的任意通知。
在程式中表示的意思如下:
1 public class Main {
2 public static void main(String[] args) {
3 try {
4 // 前置 before
5 // 手動執行目標方法
6 // 後置 afterReturning
7 } catch (Exception e) {
8 // 丟擲異常通知 afterThrowing
9 } finally {
10 // 最終 after
11 }
12 }
13 }
原始碼:
5、小結
使用 <aop:config>進行配置,proxy-target-class="true",宣告時使用cglib代理;如果不宣告,Spring 會自動選擇cglib代理還是JDK動態代理。
SpringAOP 的具體載入步驟:
①當 spring 容器啟動的時候,載入 spring 的配置檔案。
②為配置檔案中的所有 bean 建立物件。
③spring 容器會解析 aop:config 的配置,解析切入點表示式,用切入點表示式和納入 spring 容器中的 bean 做匹配,如果匹配成功,則會為該 bean 建立代理物件,代理物件的方法 = 目標方法 + 通知;如果匹配不成功,不會建立代理物件。
④在客戶端利用 context.getBean() 獲取物件時,如果該物件有代理物件,則返回代理物件;如果沒有,則返回目標物件
說明:如果目標類沒有實現介面,則 spring 容器會採用 cglib 的方式產生代理物件,如果實現了介面,則會採用 jdk 的方式。