Spring5(六)——AspectJ(xml)

一、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 的方式。

 

Tags: