天天看點

4、基于XML的AOP配置

一、面向切面程式設計(AOP)

1、AOP概述

1、面向切面程式設計(Aspect Oriented Programming,簡稱AOP)通過提供另一種思考程式結構的方式來補充面向對象程式設計 (Object Oriented Programming,簡稱OOP)。

2、OOP中子產品化的關鍵單元是類,而在AOP中子產品化的單元是切面。切面支援跨多種類型和對象的關注點(在AOP中通常稱為“橫切”關注點,例如事務管理)的子產品化。

3、在OOP程式設計模式中,OOP将不同的業務對象的抽象成為一個個的類,不同的業務操作抽象成不同的方法,這樣的好處是能更加清晰高效的邏輯單元劃分!一個完整的業務邏輯就是調用不同的對象、方法來組合完成,這類似于流水線,核心業務和非核心業務都在裡面,每一個步驟按照順序執行。這樣看來,業務邏輯之間的耦合關系非常嚴重,核心業務的代碼之間通常需要手動嵌入大量非核心業務的代碼,比如日志記錄、事務管理。對于這種跨對象和跨業務的重複的、公共的非核心邏輯,OOP沒有特别好的處理方式。

4、在AOP程式設計模式中,AOP能将不同業務流程中的相同的非核心業務邏輯從源代碼中徹底抽離出來,形成一個獨立的服務(比如日志記錄、權限校驗、異常處理、事物機制)。而當程式在編譯或運作的時候,又能在不修改源代碼的情況下,動态的選擇在程式執行流程中的某些地方,比如方法運作前後,抛出異常時等,将這些非核心服務邏輯插入到核心代碼邏輯中。

5、AOP技術讓業務中的核心子產品和非核心子產品的耦合性進一步降低,實作了代碼的複用,減少了代碼量,提升開發效率,并有利于代碼未來的可擴充性和可維護性。

2、AOP術語

1、

Aspect(切面或方面)

:切入點(Pointcut)和該位置的通知(Advice)的結合,也可以說是被抽離出來的公共業務子產品(比如:日志記錄)。 對應Java代碼中被

@AspectJ

注解标注的切面類或者在

XML

中配置的切面。

2、

Join Point(連接配接點)

:程式執行時的一些特定位置或點位,這些點位就是可能被AOP架構攔截并織入代碼的地方;連接配接點可能是類初始化、方法執行、方法調用、字段調用或處理異常等等,

Spring隻支援方法執行連接配接點

3、

Point Cut(切入點)

:用來比對要進行切入的Join point集合的表達式,通過切點表達式(pointcut expression,類似正規表達式)可以确定符合條件的連接配接點作為切入點。

4、

Advice(通知)

:在連接配接點上執行的行為,提供了在AOP中需要在切入點(Pointcut)所選擇的連接配接點(Join point)處進行擴充現有行為的手段。

5、

Introduction(引介)

:一種特殊的通知,在不修改類代碼的前提下,可以在運作期為類動态地添加一些額外的方法或屬性。

6、

Target Object(目标對象)

:需要被織入橫切關注點的對象,即該對象是切入點選擇的對象,需要被通知的對象,進而也可稱為被通知對象;由于Spring AOP 通過代理模式實作,進而這個對象永遠是被代理對象。

7、

AOP Proxy(AOP代理)

:一個類被AOP織入增強後,就會産生一個代理對象。在Spring架構中,AOP代理是JDK動态代理或CGLIB代理。

8、

Weaving(織入)

:是指把切面應用到目标對象來建立新的代理對象的過程。織入的時期可以是編譯時織入(AspectJ),也可以使用運作時織入(Spring AOP)。

3、AOP中五種通知類型

1、

Before advice(前置通知)

:在切入點方法之前執行的通知,但這個通知不能阻止切入點之前的執行流程,除非它抛出一個異常。

2、

After Returning advice(後置通知)

:在切入點方法正常執行完成後要執行的通知(比如:一個方法沒有抛出任何異常,正常傳回)。

3、

After Throwing advice(異常通知)

:在切入點方法抛出異常而退出時執行的通知。

4、

After Finally advice(最終通知)

:無論切入點方法正常傳回還是異常傳回,都要執行的通知。

5、

Around advice(環繞通知)

:可以在切入點方法調用前後完成自定義的行為。它也會選擇是否繼續執行或直接傳回它自己的傳回值或抛出異常來結束執行。

4、Spring AOP與AspectJ

1、AspectJ:是由Eclipse開源的一個AOP架構,基于Java平台,緻力于提供了最完整的AOP實作方式,官方位址。

2、Spring AOP:是Spring提供的一個AOP架構。目的并不是提供最完整的AOP實作,相反,其目的是在AOP實作和SpringIOC之間提供緊密的內建,以幫助解決企業應用程式中的大多數常見的需求和問題(方法織入)。

3、Spring支援無縫內建AspectJ架構,是以也能使用AspectJ的全部功能;Spring2.0以後新增了對AspectJ切點表達式的支援,AspectJ架構在1.5版本時,通過JDK5的注解技術引入了一批注解,比如@AspectJ、@Pointcut、相關Advice注解,支援使用注解的聲明式方式來定義切面,Spring同樣支援使用和AspectJ相同的注解。

4、Spring AOP相比于AspectJ,它的學習難度更低,更容易上手。

5、織入方式

1、

AspectJ屬于靜态織入

:它使用了專門的稱為AspectJ編譯器 (ajc) 的編譯器,在Java源碼被編譯的時候,就将切面織入到目标對象所屬類的位元組碼檔案中,并不會生成新的代理類位元組碼。是以,AspectJ在運作時不做任何事情,沒有任何額外的開銷,因為切面在類編譯的時候就織入了。

2、

Spring AOP屬于動态織入

:在運作時動态将要增強的代碼織入到目标類中,是通過

動态代理

技術完成的,如

Java JDK的動态代理

(Proxy,底層通過反射實作)或者

CGLIB的動态代理

(底層通過繼承實作),Spring AOP采用的就是基于運作時增強的代理技術。

6、JDK動态代理與CGLIB動态代理

1、

JDK動态代理

:JDK動态代理是由JDK提供的工具類Proxy實作的,動态代理類是在運作時生成指定接口的代理類,每個代理執行個體(實作需要代理的接口)都有一個關聯的調用處理程式對象,此對象實作了InvocationHandler,最終的業務邏輯是在InvocationHandler實作類的invoke方法上。

2、

CGLIB動态代理

:是一個基于ASM的位元組碼生成庫,它允許我們在運作時對位元組碼進行修改和動态生成。CGLIB通過繼承的方式實作代理。

3、兩者對比:

  • CGLIB相比于JDK動态代理更加強大,JDK動态代理隻能對接口進行代理。如果要代理的類為一個普通類、沒有接口,那麼JDK動态代理就沒法使用了。
  • CGLIB原理是針對目标類生成一個子類,覆寫其中的所有方法,是以目标類和方法不能聲明為final類型。
  • 從執行效率上看,CGLIB動态代理效率較高。
  • Spring AOP的預設代理方式是JDK代理,在SpringBoot2.x版本中預設采用的是CGLIB代理

4、請參考設計模式中的代理模式

二、XML方式實作AOP

1、引入AOP依賴

1、Spring AOP需要引入兩個依賴

spring-aop

aspectjweaver

2、可以直接引入

spring-context

依賴,因為它已經引入了其他核心的依賴

spring-aop、spring-beans、spring-core、spring-expression

等。

3、

aspectjweaver

用來解析切入點表達式的,因為Spring支援AspectJ的切入點表達式的文法。
<!--spring 核心元件所需依賴-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

<!--用于解析AspectJ的切入點表達式文法-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>
           

2、添加AOP名稱空間

1、Spring的原始配置檔案僅支援IoC的配置,如果想要使用aop的XML配置,我們需要手動引入AOP名稱空間(

xmlns:aop

),然後就能使用aop相關标簽。

2、aop标簽用于配置Spring中的所有AOP,包括Spring自己的基于代理的AOP架構和Spring與AspectJ 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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">

</beans>
           

3、簡單示例

/**
 * @Date: 2023/1/6
 * 切面類
 * 注意:切面類需要注入到IOC容器中
 */
@Component
public class LogAspect {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 異常通知
     */
    public void afterThrowing(Exception e) {
        System.out.println("異常通知,異常為:" + e.getMessage());
    }

    /**
     * 後置通知
     */
    public void afterReturning(Object result) {
        System.out.println("後置通知,傳回值為:" + result);
    }

    /**
     * 最終通知
     */
    public void afterFinally() {
        System.out.println("最終通知");
    }

    /**
     * 環繞通知
     * 一定要有ProceedingJoinPoint類型的參數
     */
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("環繞通知 --- 進入方法");
        // 需要手動放行方法,否則方法不會執行
        Object proceed = point.proceed();
        System.out.println("環繞通知 --- 退出方法");
        return proceed;
    }
}
           
/**
 * @Date: 2023/1/6
 * 目标類,也需要注入到IOC容器中
 */
@Service
public class CalculateService {
    public Integer add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i + j;
    }

    public int multiply(int i, int j) {
        return i * j;
    }

    public double divide(int i, int j) {
        return i / j;
    }
}
           
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 開啟注解掃描 -->
    <context:component-scan base-package="com.itan.aop.*" />

    <!-- aop的相關配置 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="logAspect">
            <!-- 配置切點 -->
            <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.service.*.*(..))"/>
            <!-- 環繞通知 -->
            <aop:around method="around" pointcut-ref="pointCutMethod" />
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pointCutMethod" />
            <!-- 異常通知,如果沒有異常,将不會執行增強;throwing屬性:用于設定通知第二個參數的的名稱,類型是Throwable,它是所有錯誤和異常類的頂級父類 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCutMethod" throwing="e" />
            <!-- 後置通知,returning屬性:用于設定後置通知的第二個參數的名稱,類型是Object -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
            <!-- 最終通知 -->
            <aop:after method="afterFinally" pointcut-ref="pointCutMethod" />
        </aop:aspect>
    </aop:config>
</beans>
           
/**
 * @Date: 2023/1/6
 * 測試類
 */
public class AopTest1 {
    @Test
    public void test01() {
        // 通過配置檔案建立容器對象
        ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
        CalculateService calc = context.getBean(CalculateService.class);
        calc.add(2,3);
        System.out.println("==========================");
        calc.divide(2,0);
    }
}
/**
 * 運作結果:
 * 環繞通知 --- 進入方法
 * 前置通知
 * 環繞通知 --- 退出方法
 * 後置通知,傳回值為:5
 * 最終通知
 * ==========================
 * 環繞通知 --- 進入方法
 * 前置通知
 * 異常通知,異常為:/ by zero
 * 最終通知
 */
           

4、aop:config配置

1、aop相關的配置都寫在

<aop:config>

标簽中,用于實作Spring自動代理機制。

2、

<aop:config>

标簽中可以包含

<aop:pointcut>、<aop:advisor>、<aop:aspect>

等标簽,

必須要按照該順序聲明這些标簽

3、标簽屬性說明:

  • proxy-target-class

    是否為被代理類生成CGLIB子類,隻為接口生成代理子類(即:是否使用CGlib動态代理),預設為false,使用的是JDK代理

  • expose-proxy

    是否将代理的Bean暴露給使用者,如果暴露,就可以通過AopContext類獲得,預設不暴露

5、Spring AOP使用的代理方式

1、

Spring AOP的預設代理方式是JDK動态代理,如果目标對象沒有實作任何接口,則會選擇使用CGLIB代理

2、如果實作了接口,可以通過

proxy-target-class

屬性強制使用CGLIB代理。
/**
 * @Date: 2023/1/6
 * 接口類
 */
public interface CalculateService {
    Integer add(int i, int j);

    int sub(int i, int j);

    int multiply(int i, int j);

    double divide(int i, int j);
}

/**
 * @Date: 2023/1/6
 * 接口類實作類
 */
@Service
public class CalculateServiceImpl implements CalculateService {
    public Integer add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i + j;
    }

    public int multiply(int i, int j) {
        return i * j;
    }

    public double divide(int i, int j) {
        return i / j;
    }
}
           
<!-- aop的相關配置 -->
<aop:config>
    <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.*.*.*(..))" />
    <!-- 配置切面 -->
    <aop:aspect ref="logAspect">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 最終通知 -->
        <aop:after method="afterFinally" pointcut-ref="pointCutMethod" />
    </aop:aspect>
</aop:config>
           
@Test
public void test01() {
    // 通過配置檔案建立容器對象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    CalculateService calc = context.getBean(CalculateService.class);
    System.out.println(calc.getClass());
    calc.add(2,3);
}
/**
 * 運作結果:可以看到傳回的是一個com.sun.proxy.$Proxy16類型的,說明預設使用的是JDK代理
 * class com.sun.proxy.$Proxy16
 * 前置通知
 * 最終通知
 */
           
1、強制使用CGLIB代理,修改XML中的配置為

<aop:config proxy-target-class="true">

,運作程式,發現使用CGLIB代理。
4、基于XML的AOP配置
2、目标對象不實作接口,将實作類去掉實作的接口

CalculateService

,運作程式,發現使用CGLIB代理。
4、基于XML的AOP配置

三、切面相關配置

1、aop:aspect切面

1、

<aop:aspect>

标簽用于配置切面,裡面可以包含

<aop:pointcut>、<aop:5種通知類型>

等标簽,

必須要按照該順序聲明這些标簽

2、标簽屬性說明:

  • id

    :一個切面的唯一辨別符。
  • ref

    :用于引用外部專門定義的通知Bean(也就是切面類),切面類中定義了一系列通知的方法。
  • order

    :切面的排序,當有多個通知在同一個切入點執行時,指定通知執行的先後順序,未指定該屬性時預設值為Integer.MAX_VALUE;

    值越小的切面,其内部定義的前置通知越先執行,後置通知越後執行,相同的order的切面按照切面從上到下定義順序先後執行前置通知,反向執行後置通知

/**
 * @Date: 2023/1/6
 * 第一個切面類
 */
@Component
public class OrderAspect1 {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("OrderAspect1前置通知");
    }

    /**
     * 後置通知
     */
    public void afterReturning(Object result) {
        System.out.println("OrderAspect1後置通知,傳回值為:" + result);
    }
}
           
/**
 * @Date: 2023/1/6
 * 第二個切面類
 */
@Component
public class OrderAspect2 {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("OrderAspect2前置通知");
    }

    /**
     * 後置通知
     */
    public void afterReturning(Object result) {
        System.out.println("OrderAspect2後置通知,傳回值為:" + result);
    }
}
           
<!-- aop的相關配置 -->
<aop:config>
    <aop:pointcut id="pointCutMethod" expression="execution(* com.itan.aop.service.*.*(..))" />
    <!-- 配置切面1,order值設定為1000 -->
    <aop:aspect ref="orderAspect1" order="1000">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 後置通知,returning屬性:用于設定後置通知的第二個參數的名稱,類型是Object -->
        <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
    </aop:aspect>
    <!-- 配置切面2,order值設定為100 -->
    <aop:aspect ref="orderAspect2" order="100">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointCutMethod" />
        <!-- 後置通知,returning屬性:用于設定後置通知的第二個參數的名稱,類型是Object -->
        <aop:after-returning method="afterReturning" pointcut-ref="pointCutMethod" returning="result" />
    </aop:aspect>
</aop:config>
           
@Test
public void test03() {
    // 通過配置檔案建立容器對象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    CalculateService calc = context.getBean(CalculateService.class);
    calc.add(2,3);
}
/**
 * 運作結果:可以看到切面的order值越小的前置通知先執行,後置通知最後執行
 * OrderAspect2前置通知
 * OrderAspect1前置通知
 * OrderAspect1後置通知,傳回值為:5
 * OrderAspect2後置通知,傳回值為:5
 */
           

2、配置通知

1、

<aop:aspect>

切面标簽中可以使用對應的5種通知标簽:
  • <aop:before>

    :前置通知,目标方法執行之前執行。
  • <aop:after-returning>

    :後置通知,目标方法執行之後執行,目标方法異常時,不執行;後置通知的方法能夠接收切入點方法的傳回值作為參數,隻需要配置returning屬性,returning屬性值就是後置通知方法的參數名,參數類型需要與傳回值類型比對(基本類型可以自動裝拆箱,不能自動強轉),或者向上相容(可使用父類,父接口接收),否則後置通知不會被執行。
  • <aop:after-throwing>

    :異常通知,在前置通知、目标方法和後置通知中抛出異常之後可能會執行。異常通知的方法能夠接收前置通知、切入點方法和後置通知中抛出的異常作為參數,隻需要配置throwing屬性,throwing屬性值就是異常通知方法的參數名。參數類型需要與抛出的異常類型比對或者向上相容(可使用父類、父接口接收),否則異常通知不會被執行。
  • <aop:after>

    :最終通知,無論目标方法是否正常執行,它都會在後置通知或者異常通知後面執行。
  • <aop:around>

    :環繞通知,目标方法執行前後執行,目标方法異常時,環繞後方法不執行。
2、5種通知标簽屬性說明:
  • method

    :通知的方法名,在切面類中定義的通知方法。
  • pointcut-ref

    :用于指定切入點的表達式的引用。可以通過

    <aop:ponitcut>

    單獨定義切入點。
  • pointcut

    :切入點表達式,用于指定該通知可以應用到的切入點集合。通過這個表達式可以比對到某些方法作為該通知的切入點。
  • arg-names

    :按順序使用“,”分隔的需要比對的方法參數名字元串。用于配合切入點表達式,更加細緻的比對方法,更重要的是可以進行參數值的傳遞。
  • returning

    後置通知<aop:after-returning>的特殊參數,後置通知方法能夠接收切入點方法的傳回值作為方法入參,值就是參數名稱

  • throwing

    異常通知<aop:after-throwing>的特殊參數,異常通知方法能夠接收切入帶點方法、前置通知、後置通知中抛出的異常作為方法入參,值就是參數名稱

3、環繞通知(特殊)

1、環繞通知是所有通知類型中功能最為強大的,能夠全面地控制連接配接點,甚至可以控制是否執行連接配接點方法。

2、環繞通知使用

<aop:around>

标簽配置,通常情況下,環繞通知都是獨立使用的。

3、環繞通知的方法的第一個參數類型必須是

ProceedingJoinPoint

,它是

JoinPoint

的子接口,允許控制何時執行,是否執行連接配接點方法。

4、

在環繞通知方法中需要明确調用ProceedingJoinPoint的proceed()方法來執行被代理的方法。方法proceed()的傳回值就是連接配接點方法的傳回值;通知方法的傳回值就是外部調用切入點方法擷取的最終傳回值,如果沒有傳回值,那麼外部調用切入點方法擷取的最終傳回值為null;環繞通知的傳回值類型應該和切入點方法的傳回值類型一緻或者相容

4、基于XML的AOP配置
5、proceed方法中還可以傳遞一個數組,該數組就是切入點方法所需的參數。可以通過對

ProceedingJoinPoint

調用

getArgs

擷取外部調用切入點方法時傳遞進來的參數數組,也可以在環繞通知的邏輯中自己設定參數。

4、JoinPoint

1、任何通知方法的第一個參數都可以聲明為

org.aspectj.lang.JoinPoint

類型,JoinPoint是連接配接點方法的抽象,提供了通路目前被通知方法的目标對象,代理對象,方法參數等資料方法。

2、環繞通知的參數類型應該使用

ProceedingJoinPoint

,它是

JoinPoint

的一個實作;

所有傳入的JoinPoint的實際類型都是MethodInvocationProceedingJoinPoint

3、JoinPoint的相關方法說明:

方法名 說明
String toString() 傳回連結點方法的簽名,傳回值和參數類型使用簡單類名
String toShortString() 傳回連結點方法的簡要簽名,省略傳回值、參數類型、類路徑
String toLongString() 傳回連結點方法的完整簽名,傳回值和參數類型使用全路徑名
Object getThis() 傳回目前AOP代理對象
Object getTarget() 傳回目前AOP目标對象
Object[] getArgs() 傳回目前被通知方法傳遞的實際參數值數組
Signature getSignature() 傳回目前連結點方法的簽名
SourceLocation getSourceLocation() 傳回連接配接點方法所在類檔案中的位置,相關方法不支援
String getKind() 傳回目前連接配接點的類型。Spring AOP為method-execution
StaticPart getStaticPart() 傳回連接配接點靜态部分,實際上就是傳回目前JoinPoint對象
4、ProceedingJoinPoint相關方法說明:
方法名 說明
Object proceed() throws Throwable 執行目标方法,預設使用外部傳遞的參數
Object proceed(Object[] args) throws Throwable 執行目标方法,使用該方法傳遞的數組的值作為參數

5、aop:pointcut切點表達式

1、每一個通知中,都可以配置自己的切入點表達式,使用

pointcut

屬性,很多時候切入點表達式都是一樣的,可以使用

<aop:pointcut>

标簽定義一個獨立的切入點表達式,使得多個切面和通知通過

pointcut-ref

屬性引用同一個切入點表達式。

2、

<aop:pointcut>

标簽屬性說明:
  • id

    :用于給切入點表達式提供一個唯一辨別,通過該辨別引用對應的切面表達式。
  • expression

    :用于定義切入點表達式。
3、

<aop:pointcut>

标簽作用位置:
  • 定義在

    <aop:aspect>

    标簽内部,表示該表達式隻能在目前切面内部的通知中引用。
  • 定義在

    <aop:config>

    标簽内部,表示該表達式在所有切面的所有通知中都能引用。但是要定義在

    <aop:aspect>

    标簽之前。
/**
 * @Date: 2023/1/29
 * 切面類,切面類需要注入到IOC容器中
 */
@Component
public class PointCutAspect {
    /**
     * 前置通知
     */
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 最終通知
     */
    public void afterFinally() {
        System.out.println("最終通知");
    }
}
           
/**
 * @Date: 2023/1/29
 * AOP目标切入點方法,需要注入到IOC容器中
 */
@Component
public class AopTargetPointcut {
    public void target1() {
        System.out.println("目标切入點方法1");
    }

    public void target2() {
        System.out.println("目标切入點方法2");
    }
}
           
<!-- aop的相關配置 -->
<aop:config>
    <!-- 配置一個所有切面的所有通知都能引用的表達式  aop:pointcut标簽要定義在最前面 -->
    <aop:pointcut id="p1" expression="execution(* com.itan.aop.pointcut.AopTargetPointcut.target1())" />
    <!-- 配置切面1 -->
    <aop:aspect id="asp1" ref="pointCutAspect">
        <!-- 配置前置通知,引用切點表達式 -->
        <aop:before method="before" pointcut-ref="p1" />
    </aop:aspect>
    <!-- 配置切面2 -->
    <aop:aspect id="asp2" ref="pointCutAspect">
        <!-- 配置隻能是目前切面内部的所有通知都能引用的表達式 -->
        <aop:pointcut id="p2" expression="execution(* com.itan.aop.pointcut.AopTargetPointcut.target2())" />
        <!-- 配置前置通知,引用切點表達式 -->
        <aop:before method="before" pointcut-ref="p2" />
        <!--pointcut-ref 引用切面表達式-->
        <aop:after method="afterFinally" pointcut-ref="p1" />
    </aop:aspect>
</aop:config>
           
@Test
public void test03() {
    // 通過配置檔案建立容器對象
    ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
    AopTargetPointcut atp = context.getBean(AopTargetPointcut.class);
    // 調用方法
    atp.target1();
    System.out.println("============================");
    atp.target2();
}
/**
 * 運作結果如下:調用target1方法會執行前置和後置通知,調用target2方法隻會執行前置通知,與配置保持一緻
 * 前置通知
 * 目标切入點方法1
 * 最終通知
 * ============================
 * 前置通知
 * 目标切入點方法2
 */
           

四、切入點聲明規則

1、切入點訓示符

1、前面定義切點表達式時使用了大量的execution表達式,其中execution就是一個切入點訓示符(pointcut designators,簡稱PCD),由于在Spring AOP中目前隻支援方法調用作為連接配接點,是以Spring AOP的切入點訓示符僅比對方法執行的連接配接點。

2、Spring 5.x的AOP支援使用如下切入點訓示符:

  • execution

    :通過比對某些類型的某些方法簽名來比對連接配接點方法。
  • within

    :限定比對特定類型的連接配接點方法。
  • this

    :通過比對AOP的代理對象的類型來比對連接配接點方法。
  • target

    :通過比對AOP的目标對象類型來比對連接配接點方法。
  • args

    :通過比對方法參數的數量、類型、順序來比對連結點方法。
  • bean

    :通過比對指定Bean執行個體内的連接配接點方法。
  • @target

    :通過比對類型上的某些注解類型來比對連接配接點方法。
  • @args

    :通過比對方法參數的所屬類型上的某些注解來比對連結點方法。
  • @within

    :通過比對類型及子類型上的某些注解類型來比對連接配接點方法。
  • @annotation

    :通過比對方法上的某些注解類型類比對連接配接點方法。
3、切入點表達式還支援如下通配符:
  • *

    :任意數量的字元。
  • ..

    :包名與類名之間使用表示該包及其子包下的所有類;參數清單中使用表示任意參數。
  • +

    :該類型及其子類型。
4、切入點表達式還支援運算符:
  • &&(and)

    :表示兩個條件都要比對。
  • ||(or)

    :表示兩個條件任意比對一個。
  • !(not)

    :表示不能比對該條件。

2、常用的execution訓示符

1、execution的切入點表達式使用方法的簽名來比對切入點方法,是使用最多的一種方式。

2、文法格式說明:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

4、基于XML的AOP配置
  • modifiers-pattern

    :方法的通路修飾符(可選)。
  • ret-type-pattern

    :方法的傳回值類型全路徑名,java.lang包下的類可簡寫類名。
  • declaring-type-pattern

    :方法所屬類的全路徑名(可選)。
  • name-pattern

    :方法名。
  • param-pattern

    :方法的參數清單類型的全路徑類名,多個參數類型使用逗号分隔,java.lang包下的可以簡寫成類名,按照指定順序比對。
  • throws-pattern

    :方法的異常類型的全路徑類名,多個參數類型使用逗号分隔,java.lang包下的可以簡寫成類名(可選)。
  • 帶有?的表示可選項,表達式中比對的類型都是聲明的類型,而不是實際類型,且不會向下相容,可以使用'+'向下相容

// 任意公共方法的執行:
execution(public * *(..))

// 任何一個名字以“set”開始的方法的執行:
execution(* set*(..))

// AccountService接口定義的任意方法的執行:
execution(* com.itan.service.AccountService.*(..))

// 在service包中定義的任意方法的執行:
execution(* com.itan.service.*.*(..))

// 在service包或其子包中定義的任意方法的執行:
execution(* com.itan.service..*.*(..))

// 在service包中的任意連接配接點(在Spring AOP中隻是方法執行):
within(com.itan.service.*)

// 在service包或其子包中的任意連接配接點(在Spring AOP中隻是方法執行):
within(com.itan.service..*)

// 實作了AccountService接口的代理對象的任意連接配接點 (在Spring AOP中隻是方法執行):
this(com.itan.service.AccountService)// 'this'在綁定表單中更加常用

// 實作AccountService接口的目标對象的任意連接配接點 (在Spring AOP中隻是方法執行):
target(com.itan.service.AccountService) // 'target'在綁定表單中更加常用

/**
 * 任何一個隻接受一個參數,并且運作時所傳入的參數是Serializable 接口的連接配接點(在Spring AOP中隻是方法執行)
 * 'args'在綁定表單中更加常用; 請注意在例子中給出的切入點不同于execution(* *(java.io.Serializable)): 
 * args版本隻有在動态運作時候傳入參數是Serializable時才比對,而execution版本在方法簽名中聲明隻有一個 Serializable類型的參數時候比對。
 */
args(java.io.Serializable)

// 目标對象中有一個 @Transactional 注解的任意連接配接點 (在Spring AOP中隻是方法執行)
@target(org.springframework.transaction.annotation.Transactional)// '@target'在綁定表單中更加常用

// 任何一個目标對象聲明的類型有一個 @Transactional 注解的連接配接點 (在Spring AOP中隻是方法執行):
@within(org.springframework.transaction.annotation.Transactional) // '@within'在綁定表單中更加常用

// 任何一個執行的方法有一個 @Transactional 注解的連接配接點 (在Spring AOP中隻是方法執行)
@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在綁定表單中更加常用

// 任何一個隻接受一個參數,并且運作時所傳入的參數類型具有@Classified 注解的連接配接點(在Spring AOP中隻是方法執行)
@args(com.itan.security.Classified) // '@args'在綁定表單中更加常用

// 任何一個在名為'tradeService'的Spring bean之上的連接配接點 (在Spring AOP中隻是方法執行)
bean(tradeService)

// 任何一個在名字比對通配符表達式'*Service'的Spring bean之上的連接配接點 (在Spring AOP中隻是方法執行)
bean(*Service)
           

繼續閱讀