天天看點

SpringAOP看這篇就夠了

文章目錄

      • 一、了解AOP思想
      • 二、AspectJ和SpringAOP的差別與聯系
        • 1.AspectJ架構
        • 2.SpringAOP
        • 3.總結
      • 三、AOP操作術語
      • 四、Spring AOP的使用
        • 1.XML方式
          • 1.建立用于攔截的Bean
          • 2.編寫通知類
          • 3.編寫XML配置檔案AOP
          • 4.測試類
          • 5.執行結果
        • 2.注解方式
          • 1.引入AspectJ包
          • 2.建立用于攔截的Bean
          • 3.建立Advisor
          • 4.建立配置檔案
          • 5.測試
          • 6.執行結果
        • 3.小結
        • 4.AOP execution表達式
      • 五、Spring AOP五種通知的執行順序
        • 1.XML配置方式
          • (1)測試前置、後置、環繞通知、後置傳回、異常--未抛出異常執行結果--目标方法有傳回值
          • (2)測試前置、後置、環繞通知、後置傳回、異常--未抛出異常執行結果--目标方法無傳回值
          • (3)測試前置、後置、環繞通知、後置傳回、異常--抛出異常執行結果--目标方法有傳回值
          • (4)測試前置、後置、環繞通知、後置傳回、異常--抛出異常執行結果--目标方法無傳回值
        • 2.注解方式
          • (1)測試前置、後置、環繞通知、後置傳回、異常--未抛出異常執行結果--目标方法有傳回值
          • (2)測試前置、後置、環繞通知、後置傳回、異常--未抛出異常執行結果--目标方法無傳回值
          • (3)測試前置、後置、環繞通知、後置傳回、異常--抛出異常執行結果--目标方法有傳回值
          • (4)測試前置、後置、環繞通知、後置傳回、異常--抛出異常執行結果--目标方法無傳回值
        • 3.小結

一、了解AOP思想

我們知道在面向對象OOP程式設計存在一些弊端,當需要為多個不具有繼承關系的對象引入同一個公共行為時,例如日志、安全監測等,我們隻有在每個對象裡引入公共行為,這樣程式中就産生了大量的重複代碼,是以有了面向對象程式設計的補充,面向切面程式設計(AOP)。

使用AOP技術,可以将一些系統性相關的程式設計工作或重複代碼,獨立提取出來獨立實作,然後通過切面切入程式中,可以讓開發者更專注于業務邏輯的實作,減少了大量重複代碼,提高了效率和可維護性。

AOP(Aspect Oriented Programming),它是面向對象程式設計的一種補充,主要應用于處理一些具有橫切性質的系統級服務,如日志收集、事務管理、安全檢查、緩存、對象池管理等。
輕松了解AOP思想

AOP實作的關鍵就在于AOP架構自動建立的AOP代理,AOP代理則可分為 靜态代理 和 動态代理 兩大類,其中靜态代理是指使用AOP架構提供的指令進行編譯,進而在編譯階段就可生成 AOP 代理類,是以也稱為 編譯時增強;而動态代理則在運作時借助于JDK動态代理、CGLIB等在記憶體中 “臨時” 生成AOP動态代理類,是以也被稱為 運作時增強 。

二、AspectJ和SpringAOP的差別與聯系

在提到AOP時我們總會想到SpringAOP,而在提到SpringAOP時又總會跟AspectJ扯上關系,那麼這兩者之間究竟有什麼關系呢?

1.AspectJ架構

AspectJ是一個面向切面的架構,它擴充了Java語言,定義了AOP文法,能夠在編譯器提供代碼的織入,是以它有一個專門的編譯器用來生成遵守位元組碼規範的Class檔案。

AspectJ屬于靜态織入,通過修改代碼來實作,有如下幾個織入的時機:

​1.編譯期織入(Compile-time weaving)

如類 A 使用 AspectJ 添加了一個屬性,類 B 引用了它,這個場景就需要編譯期的時候就進行織入,否則沒法編譯類 B。

2.編譯後織入(Post-compile weaving)

也就是已經生成了.class 檔案,或已經打成 jar 包了,這種情況我們需要增強處理的話,就要用到編譯後織入。

3.類加載後織入(Load-time weaving)

指的是在加載類的時候進行織入,要實作這個時期的織入,有幾種常見的方法。

  • 1)自定義類加載器來幹這個,這個應該是最容易想到的辦法,在被織入類加載到 JVM 前去對它進行加載,這樣就可以在加載的時候定義行為了。
  • 2)在 JVM 啟動的時候指定 AspectJ 提供的 agent:

    -javaagent:xxx/xxx/aspectjweaver.jar。

2.SpringAOP

衆所周知,Spring兩大特色或者說是核心即 IoC 和 AOP,AspectJ架構提供了一套完整的AOP解決方案,而SpringAOP的目的并不是為了提供最完整的AOP實作 (雖然SpringAOP具有相當的能力),而是要幫助解決企業應用中的常見問題,提供一個AOP實作與Spring IOC之間的緊密內建,能夠處理業務中的橫切關注點。

1.SpringAOP 是基于動态代理來實作橫切的,代理的方式提供了兩種:

  • 基于JDK的動态代理

    必須是面向接口的,隻有實作了具體接口的類才能生成代理對象。

  • 基于CGLIB動态代理

    對于沒有實作了接口的類,也可以産生代理,産生這個類的子類的方式。

2.SpringAop需要依賴IOC容器來管理,并且隻能作用于Spring容器,使用純Java代碼實作。

3.在性能上,由于SpringAop是基于動态代理來實作的,在容器啟動時需要生成代理執行個體,在方法調用上也會增加棧的深度,使得性能不如AspectJ那麼好。

SpringAOP看這篇就夠了

3.總結

總的來說,兩者都是AOP思想的一種實作,但是在面向切面程式設計關注的點不同:AspectJ 可以做 Spring AOP 幹不了的事情,它是AOP程式設計的完全解決方案;Spring AOP則緻力于解決企業級開發中最普遍的AOP(方法織入),而不是成為像 AspectJ 一樣的AOP方案。因為AspectJ 在實際運作之前就完成了織入,是以說它生成的類是沒有額外運作時開銷的。

相同點和差別我們都知道了,而兩者之間有什麼聯系呢,為什麼在使用Spring AOP的時候還需要(非必須)導入AspectJ的包呢?

曾經以為 AspectJ 是 Spring AOP 一部分,但其實是Spring通過內建AspectJ實作了以注解的方式定義切面,這樣大大減少了配置檔案的工作量,是以使用注解方式需要導入包。

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.5</version>
</dependency>
           

使用了

@Aspect

來定義切面,使用

@Pointcut

來定義切入點,使用Advice來定義增強處理:

@Before

@AfterReturning

@AfterThrowing

@After

@Around

,這幾個注解都是在

org.aspectj.lang.annotation

包中。雖然使用了Aspect的Annotation,但是并沒有使用它的編譯器和織入器。

此外,因為Java的反射機制無法擷取方法參數名,Spring還需要利用輕量級的位元組碼處理架構asm (已內建在Spring Core子產品中) 處理@AspectJ中所描述的方法參數名。

三、AOP操作術語

  • 1.通知(Advice):就是你想要的功能(切面要完成的功能),也就是上文說的日志、事務、安全等。先定義好,然後在想用的地方使用。

    Spring切面可以應用五種通知類型:

  1. 前置通知(Before):在目标方法被調用之前調用通知功能。
  2. 後置通知(After)(最終通知):在方法傳回前執行通知,就算目标方法抛出異常,後置通知也會執行,即目标方法無論執行成功與否最終都會執行。
  3. 後置傳回通知(After-Returning):在方法執行return後執行的,這個是不可能可以修改方法的傳回值的,這裡需要注意和後置通知不同,目标方法抛出異常,後置傳回通知不會執行,即目标方法成功執行完畢并return後才會執行。
  4. 異常通知(After-throwing):在目标方法抛出異常後調用通知。
  5. 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。
  • 2.連接配接點(JoinPoint):程式執行的某個特定位置:如類開始初始化前、類初始化後、類某個方法調用前、調用後、方法抛出異常後。一個類或一段程式代碼擁有一些具有邊界性質的特定點,這些特定點就稱為連接配接點。

    簡單來說,就是Spring允許你使用通知的地方,基本每個方法的前後(兩者都有也行),或抛出異常時都可以是連接配接點,Spring隻支援方法連接配接點,其它如AspectJ還可以讓你在構造器或屬性注入時都行,隻要記住,和方法有關的前前後後(抛出異常),都是連接配接點。

  • 3.切入點(PointCut):上面說的連接配接點的基礎上,來定義切入點,你的一個類裡有15個方法,那就有幾十個連接配接點了,但是你并不想在所有方法附近都使用通知(使用稱為“織入”,下文有詳細說明),你隻想讓其中的幾個,在調用這幾個方法之前/之後/抛出異常時幹點什麼,那麼就用切點來定義這幾個方法,讓切點來篩選連接配接點,選中那幾個你想要增強的方法。
  • 4.切面(Aspect):切面是通知和切入點的結合。其實沒連接配接點什麼事,連接配接點就是為了讓我們好了解切點,明白這個概念就可以。通知說明了幹什麼和什麼時候幹(什麼時候通過方法名中的before/after/around就能知道),而切入點說明了在哪幹(指定到底是哪個方法),這就是一個完整切面定義。
  • 5.目标對象(Target):通知邏輯的織入目标類,如果沒有AOP,目标業務類需要自己實作所有業務邏輯,而在AOP的幫助下,目标業務類隻實作那些非橫切邏輯的程式邏輯,而性能監視和事務管理等這些橫切邏輯則可以使用AOP動态織入到特定的連接配接點上。
  • 6.引介(Introduction):是一種特殊的通知,在不修改源代碼的前提下,它可以在運作期為類動态地添加一些屬性和方法。
  • 7.織入(weaving):織入是将通知添加到目标類具體連接配接點上的過程,AOP像一台織布機,将目标類、通知或引介通過AOP這台織布機

    天衣無縫的編制到一起,AOP有三種織入的方式:

    1)編譯器織入,這要求使用特殊的Java編譯器。

    2)類裝載期織入,這要求使用特殊的類裝載器。

    3)動态代理織入,在運作期為目标類添加通知生成子類的方式。

    把切面應用到目标對象來建立新的代理對象的過程,Spring采用動态代理織入,而AspectJ采用編譯期織入和類裝載期織入。

  • 8.代理(proxy):一個類被織入通知後,就産出了一個結果類,它是融合了原類和通知邏輯的代理類。根據不同的代理方式,代理類既可能是和原類具有相同接口的類,也可能是原類的子類,是以我們可以采用調用原類相同的方式調用代理類。

四、Spring AOP的使用

1.XML方式

1.建立用于攔截的Bean
public class TestBean {

    private String message="test bean";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.getMessage());
    }
}
           
2.編寫通知類
public class TestAdvisor {

    /**
     * 前置通知
     */
    public void beforeAdvisor(){
        System.out.println("beforeAdvisor");
    }

    /**
     * 後置通知
     */
    public void afterAdvisor(){
        System.out.println("afterAdvisor");
    }

    /**
     * 環繞通知
     * @param joinPoint
     */
    public void aroundAdvisor(ProceedingJoinPoint joinPoint){
        System.out.println("aroundAdvisor.....before");
        Object o = null;
        try{
            //執行proceed方法的作用就是讓目标方法執行,環繞通知=前置+目标方法執行+後置通知
            o = joinPoint.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("aroundAdvisor.....after");
    }
}

           
3.編寫XML配置檔案AOP
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="test" class="com.xmlaop.TestBean">
        <property name="message" value="測試XML配置方式實作AOP"/>
    </bean>

    <!-- 聲明增強方法所在的bean -->
    <bean id="theAdvisor" class="com.xmlaop.TestAdvisor"/>
    
    <!-- 配置切面 -->
    <aop:config>
        <!--定義切入點  -->
        <aop:pointcut id="pointcut" expression="execution(* *.test(..))"/>
        <!--引用包含增強方法的Bean  -->
        <aop:aspect ref="theAdvisor">
        	 <!--前置通知-->
            <aop:before method="beforeAdvisor" pointcut-ref="pointcut"/>
             <!--後置通知-->
            <aop:after method="afterAdvisor" pointcut-ref="pointcut"/>
            <!--環繞通知-->
            <aop:around method="aroundAdvisor" pointcut-ref="pointcut" arg-names="joinPoint"/>
        </aop:aspect>
    </aop:config>
</beans>
           
4.測試類
public class TestAOP {

    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean testBean=(TestBean)context.getBean("test");
        testBean.test();
    }
}
           
5.執行結果
SpringAOP看這篇就夠了

2.注解方式

1.引入AspectJ包
<!-- aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>
           
2.建立用于攔截的Bean
public class TestBean {
    private String message = "test bean";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.message);
    }
}

           
3.建立Advisor

在AspectJTest類中,我們要做的就是在所有類的test方法執行前在控制台beforeTest。而在所有類的test方法執行後列印afterTest,同時又使用環繞的方式在所有類的方法執行前後在此分别列印before1和after1,以下是AspectJTest的代碼:

@Aspect
public class AspectJTest {
    @Pointcut("execution(* *.test(..))")
    public void test(){
    }
    
    @Before("test()")
    public void beforeTest(){
        System.out.println("beforeTest");
    }
    
    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint p){
        System.out.println("around.....before");
        Object o = null;
        try{
            o = p.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("around.....after");
        return o;
    }
    
    @After("test()")
    public void afterTest()
    {
        System.out.println("afterTest");
    }
 }
           
4.建立配置檔案

要在Spring中開啟AOP功能,,還需要在配置檔案中作如下聲明:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy/>
    <bean id="test" class="com.yhl.myspring.demo.aop.TestBean">
        <property name="message" value="這是一個苦逼的程式員"/>
    </bean>
    <bean id="aspect" class="com.yhl.myspring.demo.aop.AspectJTest"/>
</beans>
           
5.測試
public class Test {
    public static void main(String[] args) {
        ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean bean = (TestBean)bf.getBean("test");
        bean.test();
    }
}
           
6.執行結果
SpringAOP看這篇就夠了

看到這裡細心的你可能注意到了兩種方式同樣都使用了前置、後置和環繞,但是通知的執行順序卻不一樣,下文中我們再探讨。

這篇文章也很不錯,可以做個參考。

SpringAOP的兩種配置方式詳細示例

3.小結

如果項目采用JDK5.0以上版本,可以考慮使用 @AspectJ 注解方式,減少配置的工作量;如果不願意使用注解或項目采用的JDK版本較低而無法使用注解,則可以選擇使用

<aop:aspect>

配合普通JavaBean的形式。

4.AOP execution表達式

先來看一個表達式

execution(* com.sample.service.impl..*.*(..))
           

解釋如下

符号 含義
execution() 表達式的主體
第一個 * 号 表示傳回值可以是任意類型
com.sample.service.impl AOP所切的服務的包名,即,我們的業務部分
包名後面的兩個點 表示目前包及子包
**第二個 *** 表示類名,即所有類
.*(點點) 表示 任何方法名,括号表示參數,兩個點表示任何參數類型

AspectJ中的exection表達式基本文法格式為:

SpringAOP看這篇就夠了

除了傳回類型模式、方法名模式和參數模式外,其它項都是可選的。

下面,我們給出各種使用execution()函數執行個體。

1)通過方法簽名定義切點

execution(public * *(..))
           

比對所有目标類的public方法。第一個

*

代表傳回類型,第二個

*

代表方法名,而

..

代表任意入參的方法;

execution(* *To(..))
           

比對目标類所有以To為字尾的方法。第一個

*

代表傳回類型,而

*To

代表任意以To為字尾的方法;

2)通過類定義切點

execution(* com.test.Waiter.*(..))
           

比對Waiter的所有方法。第一個

*

代表傳回任意類型,

com.test.Waiter.*

代表Waiter類中的所有方法。

execution(*com.test.Waiter+.*(..))
           

比對Waiter類及其所有實作類的方法。

3)通過類包定義切點

在類名模式串中,

*

表示包下的所有類,而

..

表示包、子孫包下的所有類。

execution(* com.test.*(..))
           

比對com.test包下所有類的所有方法;

execution(* com.test..*(..))
           

比對com.test包、子孫包下所有類的所有方法,如

com.test.dao

com.test.service

包下的所有類的所有方法都比對。

..

出現在類名中時,後面必須跟

*

,表示包、子孫包下的所有類;

execution(* com..*.*Dao.find*(..))
           

比對包名字首為com的任何包下類名字尾為Dao的方法,方法名必須以find為字首。如

com.test.UserDao.findByUserId()

com.test.dao.TestDao#findAll)

的方法都比對切點。

4)通過方法入參定義切點

切點表達式中方法入參部分比較複雜,可以使用

*

..

通配符,其中

*

表示任意類型的參數,而

..

表示任意類型參數且參數個數不限。

execution(* joke(String,int))
           

比對

joke(String,int)

方法,且

joke()

方法的第一個入參是String,第二個入參是int。如果方法中的入參類型是Java.lang包下的類,可以直接使用類名,否則必須使用全限定類名,如

joke(java.util.List,int)。

execution(* joke(String,*))
           

比對目标類中的

joke()

方法,該方法第一個入參為String,第二個入參可以是任意類型,如

joke(String s1,String s2)

joke(String s1,double d2)

都比對,但

joke(String s1,double d2,String s3)

則不比對;

execution(* joke(String,..))
           

比對目标類中的joke()方法,該方法第一個入參為String,

..

表示後面可以有任意個入參且入參類型不限,如

joke(String s1)

joke(String s1,String s2)

joke(String s1,double d2,Strings3)

都比對。

execution(* joke(Object+))
           

比對目标類中的

joke()

方法,方法擁有一個入參,且入參是Object類型或該類的子類。它比對

joke(String s1)和joke(Client c)

。如果我們定義的切點是

execution(*joke(Object))

,則隻比對

joke(Object object)

而不比對

joke(String str)

joke(Client c)

五、Spring AOP五種通知的執行順序

這裡分别用XML配置方式和注解方式對SpringAOP五種通知:
  • @Before前置通知
  • @After 後置通知
  • @Around 環繞通知
  • @AfterReturning 後置傳回通知
  • @AfterThrowing 異常通知
在代碼有無異常和目标方法有無傳回值的情況做了測試,這裡貼出執行結果并做一個總結。

1.XML配置方式

(1)測試前置、後置、環繞通知、後置傳回、異常–未抛出異常執行結果–目标方法有傳回值

執行結果

beforeAdvisor
17:24:39.678 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
測試XML配置方式實作AOP
17:24:39.700 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterReturnindAdvisor
aroundAdvisor.....after
17:24:39.700 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor
           

執行順序:

前置通知

環繞開始

目标方法執行

後置傳回通知

環繞結束

後置通知

(2)測試前置、後置、環繞通知、後置傳回、異常–未抛出異常執行結果–目标方法無傳回值

執行結果

beforeAdvisor
17:33:47.846 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
測試XML配置方式實作AOP
17:33:47.868 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterReturnindAdvisor
aroundAdvisor.....after
17:33:47.868 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor
           

執行順序:

前置通知

環繞開始

目标方法執行

後置傳回通知

環繞結束

後置通知

(3)測試前置、後置、環繞通知、後置傳回、異常–抛出異常執行結果–目标方法有傳回值

執行結果

beforeAdvisor
17:28:26.589 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
測試XML配置方式實作AOP
17:28:26.651 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
exceptionAdvisor
java.lang.ArithmeticException: / by zero
	at com.xmlaop.TestBean.test(TestBean.java:22)
	at com.xmlaop.TestBean$$FastClassBySpringCGLIB$$4b9bd518.invoke(<generated>)
aroundAdvisor.....after
17:28:26.654 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:643)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
           

執行順序:

前置通知

環繞開始

目标方法執行

目标方法報錯,執行異常通知

環繞結束

後置通知

(4)測試前置、後置、環繞通知、後置傳回、異常–抛出異常執行結果–目标方法無傳回值

執行結果

beforeAdvisor
17:36:14.319 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
測試XML配置方式實作AOP
17:36:14.341 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
exceptionAdvisor
java.lang.ArithmeticException: / by zero
	at com.xmlaop.TestBean.test(TestBean.java:22)
	at com.xmlaop.TestBean$$FastClassBySpringCGLIB$$4b9bd518.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
aroundAdvisor.....after
17:36:14.343 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor
           

執行順序:

前置通知

環繞開始

目标方法執行

目标方法報錯,執行異常通知

環繞結束

後置通知

2.注解方式

(1)測試前置、後置、環繞通知、後置傳回、異常–未抛出異常執行結果–目标方法有傳回值

執行結果

around.....before
beforeTest
這是一個苦逼的程式員
around.....after
afterTest
afterReturnindAdvisor
           

執行順序:

環繞開始

前置通知

目标方法執行

環繞結束

後置通知

傳回通知

(2)測試前置、後置、環繞通知、後置傳回、異常–未抛出異常執行結果–目标方法無傳回值

執行結果

around.....before
beforeTest
這是一個苦逼的程式員
around.....after
afterTest
afterReturnindAdvisor
           

執行順序:

環繞開始

前置通知

目标方法執行

環繞結束

後置通知

傳回通知

(3)測試前置、後置、環繞通知、後置傳回、異常–抛出異常執行結果–目标方法有傳回值

執行結果

around.....before
beforeTest
這是一個苦逼的程式員
afterTest
exceptionAdvisor==========/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.aop.TestBean.test(TestBean.java:22)
	at com.aop.TestBean$$FastClassBySpringCGLIB$$f5e4153b.invoke(<generated>)
           

執行順序:

環繞開始

前置通知

目标方法執行

後置通知

異常通知

(4)測試前置、後置、環繞通知、後置傳回、異常–抛出異常執行結果–目标方法無傳回值

執行結果

around.....before
beforeTest
這是一個苦逼的程式員
afterTest
exceptionAdvisor==========/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.aop.TestBean
           

執行順序:

環繞開始

前置通知

目标方法執行

後置通知

異常通知

3.小結

從執行結果來看,有以下幾點

1.無論使用XML配置還是注解方式,目标方法執行有無異常,有無傳回值,後置通知即@After都會執行。

2.無論使用XML配置還是注解方式,目标方法執行異常,後置傳回通知@AfterReturning不會執行,這裡需要注意的是,如果對異常進行了捕獲,異常通知将不執行,而後置傳回通知會執行。

3.在進行統一異常處理時,異常通知的異常類型級别必須大于或等于程式實際運作抛出的異常,不然捕獲不到,異常通知也就不會執行。

4.XML配置方式和注解方式執行順序還是存在差異。

XML配置方式執行順序在正常情況下(即執行無異常,無論有無傳回值)

前置通知
環繞開始
目标方法執行
後置傳回通知
環繞結束
後置通知
           

XML配置方式執行順序(執行異常未捕獲,無論有無傳回值)

前置通知
環繞開始
目标方法執行
目标方法報錯,執行異常通知【與無異常相比,這裡後置傳回通知沒有執行,執行了異常通知,其他無差别】
環繞結束
後置通知
           

注解方式通知執行順序(執行無異常,無論是否有傳回值)

環繞開始
前置通知
目标方法執行
環繞結束
後置通知
傳回通知
           

注解方式通知執行順序(執行出現異常未捕獲,無論是否有傳回值)

環繞開始
前置通知
目标方法執行
後置通知
異常通知
           

參考文章

execution表達式https://blog.csdn.net/zz210891470/article/details/54175107

AOP 使用

https://www.cnblogs.com/java-chen-hao/p/11589531.html

輕松了解 AOP思想 https://www.cnblogs.com/Wolfmanlq/p/6036019.html
面向切面程式設計 AOP https://www.zhihu.com/question/24863332

關于Spring AOP你該知曉的一切

https://zhuanlan.zhihu.com/p/25522841

整理不易,對你有幫助的話點個贊關注下謝謝。

關注我的微信公衆号【程式媛琬淇】,專注分享Java幹貨,給你意想不到的收獲。
SpringAOP看這篇就夠了