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