1. 程式人生 > 其它 >Spring學習總結(九):AspectJ 開發 AOP 的兩種方式

Spring學習總結(九):AspectJ 開發 AOP 的兩種方式

技術標籤:Spring學習springjavaaop

一、基於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();
    }