Spring學習總結(九):AspectJ 開發 AOP 的兩種方式
一、基於XML的宣告式
基於 XML 的宣告式是指通過 Spring 配置檔案的方式定義切面、切入點及宣告通知,而所有的切面和通知都必須定義在 <aop:config> 元素中。
步驟如下:
(1)匯入jar包。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency>
(2)建立Book類
public class Book {
public void addBook(){
System.out.println("add Book……");
}
public void updateBook(){
System.out.println("update Book……");
}
}
(3)建立BookProxy類
public class BookProxy { public void before(){ System.out.println("before……"); } public void after(){ System.out.println("after……"); } public void afterReturing(){ System.out.println("afterReturing……"); } public void afterThrowing(){ System.out.println("afterThrowing……"); } public void around(ProceedingJoinPoint point) throws Throwable { System.out.println("around 之前……"); point.proceed(); System.out.println("around 之後……"); } }
(4)在bean.xml中進行bean配置和aop的配置。
<!-- 基於XML的AOP實現 --> <!-- 配置bean --> <bean id="book" class="com.yht.example6.Book"></bean> <bean id="bookProxy" class="com.yht.example6.BookProxy"></bean> <!-- aop配置 --> <aop:config> <!-- expression : 配置切入點的具體位置 id:切入點的id --> <aop:pointcut id="p1" expression="execution(* com.yht.example6.Book.*(..))"/> <!-- --> <aop:aspect ref="bookProxy"> <aop:before method="before" pointcut-ref="p1"></aop:before> <aop:after method="after" pointcut-ref="p1"></aop:after> <aop:after-returning method="afterReturing" pointcut-ref="p1"></aop:after-returning> <aop:after-throwing method="afterThrowing" pointcut-ref="p1"></aop:after-throwing> <aop:around method="around" pointcut-ref="p1"></aop:around> </aop:aspect> </aop:config>
(5)進行單元測試
@Test
public void testBook(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Book book = context.getBean("book", Book.class);
book.addBook();
}
執行結果如下:
二、基於註解的宣告式
在 Spring 中,儘管使用 XML 配置檔案可以實現 AOP 開發,但是如果所有的相關的配置都集中在配置檔案中,勢必會導致 XML 配置檔案過於臃腫,從而給維護和升級帶來一定的困難。AspectJ 框架為 AOP 開發提供了另一種開發方式——基於 Annotation 的宣告式。AspectJ 允許使用註解定義切面、切入點和增強處理,而 Spring 框架則可以識別並根據這些註解生成 AOP 代理。常用註解介紹如下:
名稱 | 說明 |
---|---|
@Aspect | 用於定義一個切面 |
@Before | 用於定義前置通知,相當於BeforeAdvice |
@AfterReturning | 用於定義後置通知,相當於AfterReturningAdvice |
@Around | 用於定義環繞通知,相當於MethodInterceptor |
@AfterThrowing | 用於定義異常丟擲通知,相當ThrowAdvice |
@After | 用於定義最終(final)通知,不管是否異常,該通知都會執行 |
@DeclareParents | 用於定義引介通知,相當於IntroductionInterceptor(瞭解) |
(1)在bean.xml中配置。
<!-- 配置註解掃描的包 -->
<context:component-scan base-package="com.yht.example6"></context:component-scan>
<!--
通過aop名稱空間的<aop:aspectj-autoproxy />宣告自動為spring容器中那些配置@aspectJ切面的bean建立代理,織入切面。
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
通過aop名稱空間的<aop:aspectj-autoproxy />宣告自動為spring容器中那些配置@aspectJ切面的bean建立代理,織入切面。在<aop:aspectj-autoproxy />有一個proxy-target-class屬性,預設為false,如果proxy-target-class屬值被設定為false或者這個屬性被省略,那麼標準的JDK 基於介面的代理將起作用。當配為<aop:aspectj-autoproxy poxy-target-class="true"/>時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設定為false,如果目標類沒有宣告介面,則spring將自動使用CGLib動態代理。
(2)建立User類
@Component
public class User {
public void addUser(){
System.out.println("新增使用者");
}
public void updateUser(){
System.out.println("修改使用者");
}
public void deleteUser(){
System.out.println("刪除使用者");
}
public void searchUser(){
System.out.println("查詢使用者");
}
}
(3)建立UserProxy
@Component
@Aspect
public class UserProxy {
@Before(value = "execution(* com.yht.example6.User.addUser(..))")
public void before(){
System.out.println("方法執行之前");
}
@After(value = "execution(* com.yht.example6.User.addUser(..))")
public void after(){
System.out.println("方法執行之後");
}
@AfterReturning(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterReturning(){
System.out.println("返回後通知");
}
@AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterThrowing(){
System.out.println("丟擲異常後通知");
}
@Around(value = "execution(* com.yht.example6.User.addUser(..))")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("環繞通知——方法執行之前");
point.proceed();
System.out.println("環繞通知——方法執行之後");
}
}
(4)進行單元測試。
@Test
public void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
user.addUser();
}
執行結果如下:
幾點說明:
(1)afterThrowing方法是在程式出現異常時執行的,在User的addUser()讓程式出現異常。檢測該方法是否會執行。
@AfterThrowing(value = "execution(* com.yht.example6.User.addUser(..))")
public void afterThrowing(){
System.out.println("丟擲異常後通知");
}
製造一個NumberFormatException:
public void addUser(){
int val = Integer.parseInt("123ab");//異常
System.out.println("新增使用者");
}
結果如下:
(2)@Order的介紹
@Order 註解用來宣告元件的順序,值越小,優先順序越高,越先被執行/初始化。如果沒有該註解,則優先順序最低。對於上述程式碼,再新增一個PersonProxy類,給定@Order(1),UserProxy給定@Order(5),則執行結果PersonProxy的before方法會在UserProxy的before方法之前執行。
1)定義PersonProxy類:
@Component
@Aspect
@Order(1)
public class PersonProxy {
@Before(value = "execution(* com.yht.example6.User.addUser(..))")
public void beforeMethod(){
System.out.println("我是PersonProxy的before方法");
}
}
2) 給UserProxy新增@Order(5):
3)進行測試。得到結果如下:
(3)相同切入點的提取
在上面的例子中,UserProxy中的五種通知的配置,它們的切入點表示式是相同的,我們可以將其提取出來,然後在每個通知的註解中引用即可。所以UserProxy類就如下面:
@Component
@Aspect
@Order(5)
public class UserProxy {
@Pointcut(value = "execution(* com.yht.example6.User.addUser(..))")
public void commonMsg(){}
@Before(value = "commonMsg()")
public void before(){
System.out.println("方法執行之前");
}
@After(value = "commonMsg()")
public void after(){
System.out.println("方法執行之後");
}
@AfterReturning(value = "commonMsg()")
public void afterReturning(){
System.out.println("返回後通知");
}
@AfterThrowing(value = "commonMsg()")
public void afterThrowing(){
System.out.println("丟擲異常後通知");
}
@Around(value = "commonMsg()")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("環繞通知——方法執行之前");
point.proceed();
System.out.println("環繞通知——方法執行之後");
}
}
最終得到的結果和前面也將是一樣的。
(4)AOP純註解開發
AOP純註解開發 和IOC註解開發基本類似,提供一個配置類。加入註解:
@Configuration
@ComponentScan(basePackages = "com.yht.example6")
//@EnableAspectJAutoProxy標記在主配置類上,表示開啟基於註解的aop模式
@EnableAspectJAutoProxy
public class AopConfig {
}
測試如下:
@Test
public void testUserAnno(){
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
User user = context.getBean("user", User.class);
user.addUser();
}