Spring基礎知識(9)- Spring 整合 AspectJ
Spring AOP 是一個簡化版的 AOP 實現,並沒有提供完整版的 AOP 功能。通常情況下,Spring AOP 是能夠滿足我們日常開發過程中的大多數場景的,但在某些情況下,我們可能需要使用 Spring AOP 範圍外的某些 AOP 功能。
Spring AOP 僅支援執行公共(public)非靜態方法的呼叫作為連線點,如果我們需要向受保護的(protected)或私有的(private)的方法進行增強,此時就需要使用功能更加全面的 AOP 框架來實現,其中使用最多的就是 AspectJ。
AspectJ 是一個基於 Java 語言的全功能的 AOP 框架,它並不是 Spring 組成部分,是一款獨立的 AOP 框架。
AspectJ 支援通過 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美補充,通常情況下,我們都是將 AspectJ 和 Spirng 框架一起使用,簡化 AOP 操作。
本文在 “
AspectJ:http://www.eclipse.org/aspectj/
1. 匯入 AspectJ 依賴包
訪問 http://www.mvnrepository.com/,查詢 spring-aop, spring-aspects, aspectjweaver
本文選擇了 Spring 4.3.9.RELEASE、aspectjweaver 1.9.6。
修改 pom.xml,新增依賴包:
<project ... >
...
<dependencies>
...
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
...
</dependencies>
...
</project>
在IDE中專案列表 -> 點選滑鼠右鍵 -> Maven -> Reload Project
2. 基於XML配置實現 AspectJ 的 AOP 開發
Spring 提供了基於 XML 的 AOP 支援,並提供了一個名為 “aop” 的名稱空間,該名稱空間提供了一個 <aop:config> 元素。
標籤和含義:
(1) <aop:config> 表示一個 aop 配置;
(2) <aop:pointcut> 表示切入點;
(3) <aop:advisor> 表示通知(Advice)和切入點(Pointcut) 的組合;
(4) <aop:aspect> 表示切面,它的內部也是通知(Advice)和切入點(Pointcut) 的組合;
<aop:advisor> 和 <aop:aspect> 區別:
(1) 在面向切面程式設計時(比如用於日誌、快取等),我們一般會用 <aop:aspect>,<aop:aspect> 定義切面(包括通知和切入點);
(2) 在進行事務管理時,我們一般會用<aop:advisor>,<aop:advisor> 定義通知器 (通知器跟切面一樣,也包括通知和切入點);
(3) 如果用<aop:advisor>配置切面的話也可以配置,但切面類跟 <aop:aspect> 有所不同,需要實現介面;
配置規則:
(1) 在 Spring 配置中,所有的切面資訊(切面、切點、通知)都必須定義在 <aop:config> 元素中;
(2) 在 Spring 配置中,可以使用多個 <aop:config>;
(3) 每一個 <aop:config> 元素內可以包含 3 個子元素:<aop:pointcut>、<aop:advisor> 和 <aop:aspect> ,這些子元素必須按照這個順序進行宣告;
1) 引入 aop 名稱空間
需要在 Spring 配置檔案中匯入 Spring aop 名稱空間,如下所示。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-4.0.xsd 10 http://www.springframework.org/schema/aop 11 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 12 </beans>
2) 定義切面 <aop:aspect>
在 Spring 配置檔案中,使用 <aop:aspect> 元素定義切面。該元素可以將定義好的 Bean 轉換為切面 Bean,所以使用 <aop:aspect> 之前需要先定義一個普通的 Spring Bean。
格式如下:
<aop:config>
<aop:aspect id="myAspect" ref="com.example.MyAspect">
...
</aop:aspect>
</aop:config>
其中,id 用來定義該切面的唯一標識名稱,ref 用於引用Spring Bean。
3) 定義切入點 <aop:pointcut>
<aop:pointcut> 用來定義一個切入點,用來表示對哪個類中的那個方法進行增強。它既可以在 <aop:config> 元素中使用,也可以在 <aop:aspect> 元素下使用。
a) 作為 <aop:config> 元素的子元素定義時,表示該切入點是全域性切入點,它可被多個切面所共享;
b) 作為 <aop:aspect> 元素的子元素時,表示該切入點只對當前切面有效;
(1) 使用格式如下:
<aop:config>
<aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.*.*(..))"/>
</aop:config>
其中,id 用於指定切入點的唯一標識名稱,execution 用於指定切入點關聯的切入點表示式。
(2) execution 的用法
execution 的語法格式:
execution([許可權修飾符] [返回值型別] [類的完全限定名] [方法名稱]([引數列表])
語法說明:
a) 返回值型別、方法名、引數列表是必選項,而其它引數則為可選項。
b) 返回值型別:*表示可以為任何返回值。如果返回值為物件,則需指定全路徑的類名。
c) 類的完全限定名:指定包名.類名。
d) 方法名稱:*代表所有方法,set* 代表以 set 開頭的所有方法。
e) 引數列表:(..)代表所有引數;(*)代表只有一個引數,引數型別為任意型別;(*,String) 代表有兩個引數,第一個引數可以為任何值,第二個為 String 型別的值。
示例:
a) 對 com.example 包下 UserDao 類中的 add() 方法進行增強,配置如下:
execution(* com.example.UserDao.add(..))
b) 對 com.example 包下 UserDao 類中的所有方法進行增強,配置如下:
execution(* com.example.UserDao.*(..))
c) 對 com.example 包下所有類中的所有方法進行增強,配置如下:
execution(* com.example.*.*(..))
4) 定義通知
AspectJ 支援 5 種類型的 advice (通知/增強),格式如下:
<aop:aspect ref="myAspect">
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
<!-- 後置通知 -->
<aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
<!-- 後置返回通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
returning="returnValue"></aop:after-returning>
<!-- 異常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut"
throwing="exception"></aop:after-throwing>
<!-- 環繞通知 -->
<aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
</aop:aspect>
5) Spring 配置檔案(spring-beans.xml)
配置格式如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-4.0.xsd 10 http://www.springframework.org/schema/aop 11 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 12 13 <!-- 定義 Bean --> 14 <bean id="userDao" class="com.example.UserDaoImpl"></bean> 15 <!-- 定義切面 --> 16 <bean id="myAspect" class="com.example.MyAspect"></bean> 17 18 <aop:config> 19 <aop:pointcut id="beforePointCut" expression="execution(* com.example.UserDao.test1(..))"/> 20 <aop:pointcut id="afterPointCut" expression="execution(* com.example.UserDao.test2(..))"/> 21 <aop:pointcut id="afterReturnPointCut" expression="execution(* com.example.UserDao.test3(..))"/> 22 <aop:pointcut id="aroundPointCut" expression="execution(* com.example.UserDao.test4(..))"/> 23 <aop:pointcut id="throwPointCut" expression="execution(* com.example.UserDao.test5(..))"/> 24 <aop:aspect ref="myAspect"> 25 <!-- 前置增知 --> 26 <aop:before method="before" pointcut-ref="beforePointCut"></aop:before> 27 <!-- 後置通知 --> 28 <aop:after method="after" pointcut-ref="afterPointCut"></aop:after> 29 <!-- 後置返回知 --> 30 <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut" 31 returning="returnValue"></aop:after-returning> 32 <!-- 環繞通知 --> 33 <aop:around method="around" pointcut-ref="aroundPointCut"></aop:around> 34 <!-- 異常通知 --> 35 <aop:after-throwing method="afterThrowing" pointcut-ref="throwPointCut" 36 throwing="exception"></aop:after-throwing> 37 </aop:aspect> 38 </aop:config> 39 40 </beans>
示例:
1 package com.example; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class App { 7 public static void main( String[] args ) { 8 9 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); 10 UserDao userDao = context.getBean("userDao", UserDao.class); 11 userDao.test1(); 12 userDao.test2(); 13 userDao.test3(); 14 userDao.test4(); 15 userDao.test5(); 16 17 } 18 } 19 20 interface UserDao { 21 22 public void test1(); 23 public void test2(); 24 public String test3(); 25 public void test4(); 26 public void test5(); 27 } 28 29 class UserDaoImpl implements UserDao { 30 31 @Override 32 public void test1() { 33 System.out.println("UserDaoImpl -> test1()"); 34 } 35 36 @Override 37 public void test2() { 38 System.out.println("UserDaoImpl -> test2()"); 39 } 40 41 @Override 42 public String test3() { 43 System.out.println("UserDaoImpl -> test3()"); 44 return "OK"; 45 } 46 47 @Override 48 public void test4() { 49 System.out.println("UserDaoImpl -> test4()"); 50 } 51 52 @Override 53 public void test5(){ 54 System.out.println("UserDaoImpl -> test5()"); 55 } 56 57 } 58 59 class MyAspect { 60 61 public void before() { 62 System.out.println("MyAspect -> before()"); 63 } 64 65 public void after() { 66 System.out.println("MyAspect -> after()"); 67 } 68 69 public void afterReturning(Object returnValue) { 70 System.out.println("MyAspect -> afterReturning(): returnValue = " + returnValue); 71 } 72 73 public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 74 System.out.println("MyAspect -> around(): before"); 75 proceedingJoinPoint.proceed(); 76 System.out.println("MyAspect -> around(): after"); 77 } 78 79 public void afterThrowing(Throwable exception) { 80 System.out.println("MyAspect -> afterThrowing(): error = " + exception.getMessage()); 81 } 82 83 }
輸出:
MyAspect -> before()
UserDaoImpl -> test1()
UserDaoImpl -> test2()
MyAspect -> after()
UserDaoImpl -> test3()
MyAspect -> afterReturning(): returnValue = OK
MyAspect -> around(): before
UserDaoImpl -> test4()
MyAspect -> around(): after
UserDaoImpl -> test5()
3. 基於註解方式實現 AspectJ 的 AOP 開發
在 Spring 中,雖然我們可以使用 XML 配置檔案可以實現 AOP 開發,但如果所有的配置都集中在 XML 配置檔案中,就勢必會造成 XML 配置檔案過於臃腫,從而給維護和升級帶來一定困難。
AspectJ 框架為 AOP 開發提供了一套 @AspectJ 註解。它允許我們直接在 Java 類中通過註解的方式對切面(Aspect)、切入點(Pointcut)和增強(Advice)進行定義,Spring 框架可以根據這些註解生成 AOP 代理。
註解的介紹:
名稱 | 說明 |
@Aspect | 用於定義一個切面。 |
@Pointcut | 用於定義一個切入點。 |
@Before | 用於定義前置通知,相當於 BeforeAdvice。 |
@After | 用於定義後置通知,不管是否異常,該通知都會執行。 |
@AfterReturning | 用於定義後置返回通知,相當於 AfterReturningAdvice。 |
@Around | 用於定義環繞通知,相當於 MethodInterceptor。 |
@AfterThrowing | 用於定義丟擲通知,相當於 ThrowAdvice。 |
@DeclareParents | 用於定義引介通知,相當於 IntroductionInterceptor(不要求掌握)。 |
示例:
1 package com.example; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.ProceedingJoinPoint; 5 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 7 import org.springframework.stereotype.Component; 8 import org.springframework.context.annotation.ComponentScan; 9 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.EnableAspectJAutoProxy; 11 import org.aspectj.lang.annotation.*; 12 13 public class App { 14 public static void main( String[] args ) { 15 16 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OrderAspectConfig.class); 17 OrderDao orderDao = context.getBean("orderDao", OrderDao.class); 18 orderDao.test1(); 19 orderDao.test2(); 20 orderDao.test3(); 21 orderDao.test4(); 22 orderDao.test5(); 23 24 } 25 } 26 27 interface OrderDao { 28 29 public void test1(); 30 public void test2(); 31 public String test3(); 32 public void test4(); 33 public void test5(); 34 35 } 36 37 @Component("orderDao") 38 class OrderDaoImpl implements OrderDao { 39 40 @Override 41 public void test1() { 42 System.out.println("OrderDaoImpl -> test1()"); 43 } 44 45 @Override 46 public void test2() { 47 System.out.println("OrderDaoImpl -> test2()"); 48 } 49 50 @Override 51 public String test3() { 52 System.out.println("OrderDaoImpl -> test3()"); 53 return "SUCCESS"; 54 } 55 56 @Override 57 public void test4() { 58 System.out.println("OrderDaoImpl -> test4()"); 59 } 60 61 @Override 62 public void test5() { 63 System.out.println("OrderDaoImpl -> test5()"); 64 int i = 1 / 0; 65 } 66 } 67 68 @Configuration 69 @ComponentScan(basePackages = "com.example") //註解掃描 70 @EnableAspectJAutoProxy //開啟 AspectJ 的自動代理 71 class OrderAspectConfig { 72 73 } 74 75 @Component // 定義成 Bean 76 @Aspect // 定義為切面 77 class TestAspect { 78 @Before("execution(* com.example.OrderDao.test1(..))") 79 public void before(JoinPoint joinPoint) { 80 System.out.println("TestAspect -> before(): joinPoint = " + joinPoint); 81 } 82 83 @After("execution(* com.example.OrderDao.test2(..))") 84 public void after(JoinPoint joinPoint) { 85 System.out.println("TestAspect -> after(): joinPoint = " + joinPoint); 86 } 87 88 @AfterReturning(value = "execution(* com.example.OrderDao.test3(..))", returning = "returnValue") 89 public void afterReturning(Object returnValue) { 90 System.out.println("TestAspect -> afterReturning(): returnValue = " + returnValue); 91 } 92 93 @Pointcut(value = "execution(* com.example.OrderDao.test4(..))") 94 public void pointCut1() { 95 System.out.println("TestAspect -> pointCut1()"); 96 } 97 98 @Around("TestAspect.pointCut1()") 99 public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 100 System.out.println("TestAspect -> around(): before"); 101 proceedingJoinPoint.proceed(); 102 System.out.println("TestAspect -> around(): after"); 103 } 104 105 @Pointcut(value = "execution(* com.example.OrderDao.test5(..))") 106 public void pointCut2() { 107 System.out.println("TestAspect -> pointCut2()"); 108 } 109 110 @AfterThrowing(pointcut = "pointCut2()", throwing = "ex") 111 public void afterThrowing(JoinPoint joinPoint, Exception ex) { 112 String name = joinPoint.getSignature().getName(); 113 System.out.println("TestAspect -> afterThrowing(): name = " + name + ", error = " + ex.getMessage()); 114 } 115 }
輸出:
TestAspect -> before(): joinPoint = execution(void com.example.OrderDao.test1())
OrderDaoImpl -> test1()
OrderDaoImpl -> test2()
TestAspect -> after(): joinPoint = execution(void com.example.OrderDao.test2())
OrderDaoImpl -> test3()
TestAspect -> afterReturning(): returnValue = SUCCESS
TestAspect -> around(): before
OrderDaoImpl -> test4()
TestAspect -> around(): after
OrderDaoImpl -> test5()
TestAspect -> afterThrowing(): name = test5, error = / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
...