天天看點

Spring AOP 學習筆記一、基本概念二、底層原理三、AOP術語四、AOP依賴五、基于注解六、切入點表達式七、通知(Advice)八、完全注解開發九、基于xml檔案

一、基本概念

  • AOP,即面向切面程式設計,利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高開發的效率
  • 可以明确的定義這個功能應用在哪裡,以什麼方式應用,并且不必修改受影響的類。這樣一來橫切關注點就被子產品化到特殊的類裡——這樣的類我們通常稱之為“切面”
  • 即:不通過修改源代碼的方式,在主幹功能裡面添加新功能
  • AOP的好處:
    • 每個事物邏輯位于一個位置,代碼不分散,便于維護和更新
    • 業務子產品更簡潔,隻包含核心業務代碼

二、底層原理

  • AOP底層使用動态代理,在有接口的情況下,使用JDK動态代理;在沒有接口的情況下,使用CGLIB代理。具體的設計模式,詳見代理模式

三、AOP術語

Spring AOP 學習筆記一、基本概念二、底層原理三、AOP術語四、AOP依賴五、基于注解六、切入點表達式七、通知(Advice)八、完全注解開發九、基于xml檔案
  • 橫切關注點:從每個方法中抽取出來的同一類非核心業務。
  • 切面(Aspect):封裝橫切關注點資訊的類,每個關注點展現為一個通知方法。
  • 通知(Advice):切面必須要完成的各個具體工作
  • 目标(Target):被通知的對象
  • 代理(Proxy):向目标對象應用通知之後建立的代理對象
  • 連接配接點(Joinpoint):橫切關注點在程式代碼中的具體展現,對應程式執行的某個特定位置。例如:類某個方法調用前、調用後、方法捕獲到異常後等。

    在應用程式中可以使用橫縱兩個坐标來定位一個具體的連接配接點:

    Spring AOP 學習筆記一、基本概念二、底層原理三、AOP術語四、AOP依賴五、基于注解六、切入點表達式七、通知(Advice)八、完全注解開發九、基于xml檔案
  • 切入點(pointcut): 定位連接配接點的方式。每個類的方法中都包含多個連接配接點,是以連接配接點是類中客觀存在的事物。如果把連接配接點看作資料庫中的記錄,那麼切入點就是查詢條件——AOP可以通過切入點定位到特定的連接配接點。切點通過

    org.springframework.aop.Pointcut

    接口進行描述,它使用類和方法作為連接配接點的查詢條件。
  • 簡單來說,橫切關注點就是被抽取出來的業務,切面就是封裝業務方法的類,通知就是具體的方法。每個目标都有相對應的橫切關注點,在程式中的具體位置就是連接配接點,而連接配接點可以選擇是否切入通知,選擇被切入通知的位置叫做切入點。

四、AOP依賴

  • spring架構一般都是基于AspectJ實作AOP操作
  • 相關依賴:
    Spring AOP 學習筆記一、基本概念二、底層原理三、AOP術語四、AOP依賴五、基于注解六、切入點表達式七、通知(Advice)八、完全注解開發九、基于xml檔案
  • 或通過maven導入
<dependencies>
<!--spring AOP的包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<!--springIOC的包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
           

五、基于注解

  • 在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">
               
    <!--  開啟注解掃描  -->
    <context:component-scan base-package="com.du.spring5"/>
               
  • 開啟生成代理對象
    <!-- 開啟生成代理對象 -->
    <aop:aspectj-autoproxy/>
               
  • 通過注解

    @Component

    建立

    UserProxy

    對象,在增強類上面添加

    @Aspect

    ,說明這是一個切面對象
    @Component
    @Aspect
    public class UserProxy2 { ... }
               
  • 配置不同類型的通知。在作為通知方法上面添加通知類型注解,使用切入點表達式配置。
  • AspectJ支援5種類型的通知注解:
    • @Before

      :前置通知,在方法執行之前執行
    • @After

      :後置通知,在方法執行之後執行
    • @AfterRunning

      :傳回通知,在方法傳回結果之後執行
    • @AfterThrowing

      :異常通知,在方法抛出異常之後執行
    • @Around

      :環繞通知,圍繞着方法執行
  • 在切面上再添加一個

    @Order

    注解,可以設定多個切面的優先級,值越小優先級越大,越先運作
    @Component //通過注解配置
    @Aspect // 生成代理對象
    @Order(10)
    public class UserProxy {
    }
               

六、切入點表達式

  • 知道對哪個類裡面的哪個方法進行增強
  • 文法結構:

    execution([權限修飾符] [傳回類型] [類全路徑] [方法名稱]([參數清單]) )

6.1 例子

  • com.du.spring5.bean.User

    類裡面的

    add

    進行增強:

    execution(* com.du.spring5.bean.User.add(..))

  • com.du.spring5.bean.User

    類裡面的所有方法進行增強:

    execution(* com.du.spring5.bean.User.*(..))

  • com.du.spring5.bean

    包内的所有類的所有方法進行增強:

    execution(* com.du.spring5.bean.*.*(..))

  • ..

    比對任意數量、任意類型的參數

6.2 例子二

  • 在AspectJ中,切入點表達式可以通過 “&&”、“||”、“!”等操作符結合起來。
  • 任意類中第一個參數為int類型的add方法或sub方法:

    execution (* *.add(int,..)) || execution(* *.sub(int,..))

七、通知(Advice)

  • 在具體的連接配接點上要執行的操作
  • 一個切面可以包括一個或者多個通知。
  • 通知所使用的注解的值往往是切入點表達式。

7.1 前置通知

  • 在方法執行之前執行的通知
  • 使用

    @Before

    注解

7.2 後置通知

  • 後置通知:後置通知是在連接配接點完成之後執行的,即連接配接點傳回結果或者抛出異常的之後。可以類比成finally,無論是否正常結束,都會執行。
  • 使用

    @After

    注解

7.3 傳回通知

  • 隻有正常結束了,才會執行
  • 使用

    @AfterReturning

    注解
  • 若想擷取方法執行完之後的傳回結果,可以在參數清單中添加一個接收

    result

    的參數,示例如下:
@AfterReturning(value = "execution(* com.du.spring5.bean.*.*(..))", returning = "result")
  public void afterReturning(Object result) {
    System.out.println("方法執行結束,傳回值為" + result);
  }
           

returning

指定 傳回值賦給哪個參數,将參數名

result

賦給它

7.4 異常通知

  • 隻在連接配接點抛出異常時才執行異常通知
  • 使用

    @AfterThrowing

    注解
  • 若想捕獲異常的資訊,就可以像上面一樣,在入參中添加一個接受

    Exception

    的參數,并在注解中說明
@AfterThrowing(value = "execution(* com.du.spring5.bean.*.*(..))", throwing = "exception")
  public void afterThrowing(Exception exception) {
    System.out.println("發生了" + exception + "異常");
  }
           

7.5 JoinPoint

  • 若想在通知中接收目标方法的資訊,就可以在參數清單中添加一個

    JoinPoint

    類型的參數,Spring 會自動将該對象注入到方法中,無需在注解中說明。
@Before(value = "execution(* com.du.spring5.bean.*.*(..))")
  public void before(JoinPoint joinPoint) {
    // 擷取方法的所有輸入參數
    Object[] args = joinPoint.getArgs();
    // 擷取簽名
    Signature signature = joinPoint.getSignature();
    System.out.println("正在執行[" + signature.getName() + "]方法, 參數為" + Arrays.toString(args));
  }
           
更多細節可參考

JoinPoint

接口

7.6 @Pointcut

  • 為了避免切入點表達式重複寫,可以通過注解

    @Pointcut

    ,統一配置,統一使用。
    @Pointcut("execution(* com.du.spring5.bean.*.*(..))")
      public void pointcut() {
      }
      
      @Before(value = "pointcut()")
      public void before(JoinPoint joinPoint) {
      }
               

7.7 環繞通知

  • 環繞通知是所有通知類型中功能最為強大的,能夠全面地控制連接配接點,甚至可以控制是否執行連接配接點。
  • 很類似于反射的方法,相當于在參數中接收一個包含需要執行的方法及其參數的對象

    ProceedingJoinPoint

    ,然後通過反射的方法執行該方法。而在執行方法的周圍,可以通過添加一些

    try-catch-finally

    ,實作與上面四種通知類型的效果。
    @Around(value="pointcut()")
      public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
          System.out.println("【環繞前置通知】");
          
          Object[] args = joinPoint.getArgs(); // 擷取參數
          Object res = joinPoint.proceed(args);  // 執行方法,擷取結果
          
          System.out.println("【環繞傳回通知】,結果為" + res);
          return res;
        } catch (Throwable throwable) {
          System.out.println("【環繞異常通知】");
          throw new RuntimeException(throwable);
        } finally {
          System.out.println("【環繞後置通知】");
        }
      }
               
    記得将接收到的結果傳回出去,否則之前注解的

    @AfterReturning

    的方法、真正調用方法的地方,沒辦法擷取到執行的結果。

    攔截到的

    Exception

    ,記得也抛出去,否則其他地方也接收不到。

7.8 執行順序

  • 目前版本是

    5.2.10

    ,執行順序如下
  • 無異常:

    @Before

    -> 真正的方法 ->

    @AfterReturning

    ->

    @After

  • 存在異常:

    @Before

    -> 真正的方法 ->

    @AfterThrowing

    ->

    @After

  • 包含上述的環繞通知之後:

    環繞前置通知

    ->

    @Before

    -> 真正的方法 ->

    @AfterReturning

    ->

    @After

    ->

    環繞傳回通知

    ->

    環繞後置通知

八、完全注解開發

  • 不使用xml檔案,直接使用一個config類
@Configuration // 表示這是個配置類
@ComponentScan(basePackages = {"com.du.spring5"})  // 開啟元件掃描
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 開啟aop
public class ConfigAop {
}
           
  • 調用
@Test
  public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigAop.class);  // 配置類
    User2 user2 = context.getBean("user2", User2.class);
    user2.add();
  }
           

九、基于xml檔案

  • 除了使用AspectJ注解聲明切面,Spring也支援在bean配置檔案中聲明切面。這種聲明是通過aop名稱空間中的XML元素完成的。
  • 正常情況下,基于注解的聲明要優先于基于XML的聲明。通過AspectJ注解,切面可以與AspectJ相容,而基于XML的配置則是Spring專有的。由于AspectJ得到越來越多的 AOP架構支援,是以以注解風格編寫的切面将會有更多重用的機會。
  • 比較:較重要的配置,通過

    xml

    配置;其他配置通過注解配置。

9.1 配置案例

  • xml檔案,名稱空間添加aop相關内容
    <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"
           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"
    >
               
  • 在xml上配置aop
    • 在bean配置檔案中,所有的Spring AOP配置都必須定義在

      <aop:config>

      元素内部。對于每個切面而言,都要建立一個

      <aop:aspect>

      元素來為具體的切面實作引用後端bean執行個體。
    • 切面bean必須有一個辨別符,供

      <aop:aspect>

      元素引用。
    <aop:config>
        <!-- 設定切入點 -->
        <aop:pointcut id="user_add_pointcut" expression="execution(* com.du.spring5.bean.User.add(..))"/>
        <!--設定切面-->
        <aop:aspect ref="userProxy">
            <!-- 設定切面的位置,以及使用切面插入的方法,切入點-->
            <!-- 标簽頭說明了切入的位置,method為指定的方法,pointcut-ref為指定的切入點 -->
            <aop:before method="before" pointcut-ref="user_add_pointcut"/>
            <aop:after method="after" pointcut-ref="user_add_pointcut"/>
            <aop:after-returning method="afterReturn" pointcut-ref="user_add_pointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="user_add_pointcut"/>
            <aop:around method="around" pointcut-ref="user_add_pointcut"/>
        </aop:aspect>
    </aop:config>
               
  • 原始類
    public class User {
      private final static Logger logger = Logger.getLogger(User.class);
      public void add() {
        logger.info("add.......");
      }
    }
               
  • 代理類
    public class UserProxy {
      private final static Logger logger = Logger.getLogger(UserProxy.class);
      /**
       * 前置通知
       */
      public void before() {
        logger.info("before");
      }
      /**
       * 最終通知
       */
      public void after() {
        logger.info("after");
      }
      /**
       * 後置通知(傳回通知)
       */
      public void afterReturn() {
        logger.info("afterReturn");
      }
      /**
       * 異常通知
       */
      public void afterThrowing() {
        logger.info("afterThrowing");
      }
      /**
       * 環繞通知
       * @param proceedingJoinPoint
       * @throws Throwable
       */
      public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("around before");
        proceedingJoinPoint.proceed();
        logger.info("around ater");
      }
    }
               

9.2 配置

  • 所有的Spring AOP配置都必須定義在

    <aop:config>

    元素内部。在這個config裡面,把需要的切面類在裡面通過

    <aop:aspect>

    配置。

9.3 切面

  • <aop:aspect>

    切面裡面需要指定切面類是哪個(已經被注入到IOC容器内了),通過

    ref

    參數指定對象id;裡面還可以指定優先級

    order

9.3 切入點

  • 切入點使用

    <aop:pointcut>

    元素聲明
  • 這個标簽可以放進

    <aop:config>

    裡面,給所有切面使用,也可以放進

    <aop:aspect>

    隻給目前切面使用。
  • 該标簽需要通過

    expression

    指定切入點表達式,設定

    id

    以供别的标簽調用。

9.4 聲明通知

  • before

    通知可以通過

    <aop:before>

    标簽指定,其中,

    method

    屬性指定方法,

    pointcut-ref

    指定切入點。
  • 具體配置
<aop:config>
<!-- 設定切入點 -->
<aop:pointcut id="user_add_pointcut" expression="execution(* com.du.spring5.bean.User.add(..))"/>
<!--設定切面-->
<aop:aspect ref="userProxy">
    <!-- 設定切面的位置,以及使用切面插入的方法,切入點-->
    <!-- method為切入的位置,pointcut-ref為代理類的指定的方法 -->
    <aop:before method="before" pointcut-ref="user_add_pointcut"/>
    <aop:after method="after" pointcut-ref="user_add_pointcut"/>
    <aop:after-returning method="afterReturn" pointcut-ref="user_add_pointcut"/>
    <aop:after-throwing method="afterThrowing" pointcut-ref="user_add_pointcut"/>
    <aop:around method="around" pointcut-ref="user_add_pointcut"/>
</aop:aspect>
</aop:config>
           
上一篇: SSM整合運用
下一篇: SSM整合筆記