11、Spring之基於註解的AOP

来源:https://www.cnblogs.com/Javaer1995/archive/2023/08/15/17619156.html
-Advertisement-
Play Games

## 11.1、環境搭建 > 創建名為spring_aop_annotation的新module,過程參考[9.1節](https://www.cnblogs.com/Javaer1995/p/17610379.html "9.1節") ### 11.1.1、配置打包方式和依賴 ![image](h ...


11.1、環境搭建

創建名為spring_aop_annotation的新module,過程參考9.1節

11.1.1、配置打包方式和依賴

image

註意:AOP需要在IOC的基礎上實現,因此需要導入IOC的依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.rain</groupId>
    <artifactId>spring_aop_annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!-- Spring-IOC的依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!-- spring-AOP的依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!-- junit測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

11.1.2、創建Calculator介面及實現類

image

package org.rain.spring.aop.annotation;

/**
 * @author liaojy
 * @date 2023/8/12 - 17:43
 */
public interface Calculator {

    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);

}

image

package org.rain.spring.aop.annotation;

import org.springframework.stereotype.Component;

/**
 * @author liaojy
 * @date 2023/8/12 - 17:45
 */

// @Component註解保證這個目標類能夠放入IOC容器
@Component
public class CalculatorImpl implements Calculator {
    public int add(int i, int j) {

        int result = i + j;
        System.out.println("方法內部 result = " + result);
        return result;

    }

    public int sub(int i, int j) {

        int result = i - j;
        System.out.println("方法內部 result = " + result);
        return result;

    }

    public int mul(int i, int j) {

        int result = i * j;
        System.out.println("方法內部 result = " + result);
        return result;

    }

    public int div(int i, int j) {

        int result = i / j;
        System.out.println("方法內部 result = " + result);
        return result;

    }
}

11.1.3、創建切麵類LoggerAspect

image

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author liaojy
 * @date 2023/8/12 - 17:56
 */

// @Aspect表示這個類是一個切麵類
@Aspect
// @Component註解保證這個切麵類能夠放入IOC容器
@Component
public class LoggerAspect {
}

11.1.4、創建spring配置文件

image

<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--
        對指定的package進行掃描,將使用組件註解的類的對象(本示例是目標對象和切麵對象),交給spring的ioc容器來管理
    -->
    <context:component-scan base-package="org.rain.spring.aop.annotation"></context:component-scan>

    <!--
        開啟基於註解的AOP功能,該功能會為目標對象自動生成代理
    -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

11.2、前置通知的使用

11.2.1、基本示例

11.2.1.1、配置前置方法

image

    /*
    * @Before註解:用於將方法標識為前置通知(方法)
    * @Before註解的value屬性值為切入點表達式,其作用是將該前置通知(方法)安插到對應目標方法的連接點上
    * */
    @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
    public void beforeMethod(){
        System.out.println("LoggerAspect,前置通知");
    }

11.2.1.2、測試使用效果

image

由控制台日誌可知,切麵類的前置通知(方法),通過切入點表達式,作用到了目標方法的連接點上

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 註意:這裡不能直接獲取目標對象來使用;因為使用了AOP之後,IOC容器中就只有對應目標對象的代理對象;
        // 如果強行獲取目標對象,則報錯:NoSuchBeanDefinitionException
        //Calculator calculator = ioc.getBean(CalculatorImpl.class);

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.add(1,2);

    }

11.2.2、高級示例

11.2.2.1、改進前置方法

image

該示例中(前置)通知方法引入了連接點參數,通過連接點參數,可以動態獲取(切入點表達式)對應的目標方法的名稱和參數列表

    /*
    * @Before註解:用於將方法標識為前置通知(方法)
    * @Before註解的value屬性值為切入點表達式,其作用是將該前置通知(方法)安插到對應目標方法的連接點上
    * */
    @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
    // joinPoint參數:可以獲取(通過切入點表達式定位出的)連接點的相關信息
    public void beforeMethod(JoinPoint joinPoint){
        // 獲取連接點所對應目標方法的名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取連接點所對應目標方法的參數列表
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
    }

11.2.2.2、測試使用效果

image

11.3、切入點表達式的進階用法

11.3.1、高頻用法示例

image

    // @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")

    /**
     * 第一個*表示任意訪問修飾符和返回值類型,
     * 第二個*表示該類的任意方法名稱,
     * (..)表示方法的任意參數列表
     * 在類的位置也可以使用*,表示當前包下所有的類,
     * 在包的位置也可以使用*,表示當前包下所有的子包,
     */
    @Before("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
    }

11.3.2、詳細語法圖解

image

11.3.3、復用切入點表達式

11.3.3.1、聲明公共的切入點表達式

image

    @Pointcut("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
    public void pointCutOne(){}

11.3.3.2、在同一個切麵類中復用

image

    // @Before註解的value屬性值,可以設置為使用了@Pointcut註解標識的方法名,從而復用該@Pointcut註解定義的切入點表達式
    @Before("pointCutOne()")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
    }

11.3.3.3、在不同一個切麵類中復用

    // 復用其他切麵類中@Pointcut註解定義的切入點表達式,
	// @Before註解的value屬性值,需要設置為使用了@Pointcut註解標識的(全限定類名+)方法名
    @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
    }

11.4、其他通知的使用

11.4.1、後置通知

11.4.1.1、配置後置方法

image

    // @After註解:用於將方法標識為後置通知(方法)
    @After("pointCutOne()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->後置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
    }

11.4.1.2、測試使用效果

image

由控制台日誌可知,後置通知在目標對象方法的finally子句中執行(一般用於釋放資源)

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.div(1,0);

    }

11.4.2、返回通知

11.4.2.1、配置返回通知

image

    /**
     * @AfterReturning註解:用於將方法標識為返回通知(方法)
     *  returning屬性:指定(返回)通知方法中的某個參數(名),用於接收目標對象方法的返回值
     */
    @AfterReturning(value = "pointCutOne()",returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->返回通知,方法名:"+methodName+",結果:"+ result);
    }

11.4.2.2、測試使用效果

image

由控制台日誌可知,返回通知在目標對象方法的返回值之後執行

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.div(1,1);

    }

11.4.3、異常通知

11.4.3.1、配置異常通知

image

    /**
     * @AfterThrowing註解:用於將方法標識為異常通知(方法)
     *  throwing屬性:指定(異常)通知方法中的某個參數(名),用於接收目標對象方法出現的異常
     */
    @AfterThrowing(value = "pointCutOne()",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect-->異常通知,方法名:"+methodName+",異常:"+ ex);
    }

11.4.3.2、測試使用效果

image

由控制台日誌可知,異常通知在目標對象方法的catch子句中執行

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.div(1,0);

    }

11.4.4、通知的執行順序

11.4.4.1、Spring版本5.3.x以前

  • 前置通知

  • 目標操作

  • 後置通知

  • 返回通知或異常通知

11.4.4.2、Spring版本5.3.x以後

本示例

  • 前置通知

  • 目標操作

  • 返回通知或異常通知

  • 後置通知

11.5、環繞通知

11.5.1、配置環繞通知

image

環繞通知和動態代理的形式,非常相似

    /**
     * @Around註解:用於將方法標識為環繞通知(方法)
     *  環繞通知(方法)使用的參數是ProceedingJoinPoint類型
     *  環繞通知(方法)的返回值,必須和目標對象方法的返回值一致
     */
    @Around("pointCutOne()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        String methodName = proceedingJoinPoint.getSignature().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        Object result = null;
        try {
            System.out.println("LoggerAspect-->環繞前置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
            // 表示目標對象方法的執行
            result = proceedingJoinPoint.proceed();
            System.out.println("LoggerAspect-->環繞返回通知,方法名:"+methodName+",結果:"+ result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("LoggerAspect-->環繞異常通知,方法名:"+methodName+",異常:"+ throwable);
        }finally {
            System.out.println("LoggerAspect-->環繞後置通知,方法名:"+methodName+",參數:"+ Arrays.toString(args));
        }
        return result;
    }

11.5.2、測試使用效果

image

註意:因為環繞通知包括了其他四種通知,所以一般要麼配置其他四種通知,要麼只配置環繞通知;本示例為了展示效果才同時配置

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.div(1,1);

    }

11.6、切麵的優先順序

11.6.1、創建其他切麵類ValidateAspect

image

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author liaojy
 * @date 2023/8/15 - 7:49
 */
@Aspect
@Component
public class ValidateAspect {
}

11.6.2、配置前置通知方法

image

    @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
    public void beforeMethod(){
        System.out.println("ValidateAspect-->前置通知");
    }

11.6.3、測試使用效果

image

由控制台日誌可知,ValidateAspect切麵的前置通知方法生效了,但執行順序在LoggerAspect切麵的前置通知方法的後面

    @Test
    public void testAOPByAnnotation(){

        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        // 雖然不知道代理對象的類名,但可以通過代理對象和目標對象共同實現的介面類型來從ioc容器中獲取代理對象
        Calculator calculator = ioc.getBean(Calculator.class);

        // 只能通過代理對象來訪問目標對象中的方法
        calculator.div(1,1);

    }

11.6.4、調整切麵的優先順序

image

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author liaojy
 * @date 2023/8/15 - 7:49
 */
@Aspect
@Component
// @Order註解:用於設置切麵的優先順序,value屬性值越小,優先順序越高,預設值為Integer的最大值
@Order(2023)
public class ValidateAspect {

    @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
    public void beforeMethod(){
        System.out.println("ValidateAspect-->前置通知");
    }

}

11.6.5、測試調整後的效果

image

由控制台日誌可知,ValidateAspect切麵的前置通知方法的執行順序,在LoggerAspect切麵的前置通知方法的前面

這是因為ValidateAspect切麵的@Order註解的value屬性值已設為2023,要小於LoggerAspect切麵所使用的預設值(Integer的最大值2147483647)

11.7、擴展知識

image

  • AspectJ本質上是靜態代理,將代理邏輯“織入”被代理的目標類編譯得到的位元組碼文件,但最終效果是動態的。

  • weaver就是織入器,Spring只是借用了AspectJ中的註解。

image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Sun公司以及其他虛擬機提供商發佈了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的的程式存儲格式——位元組碼(ByteCode),從而實現了程式的“一次編寫,到處運行”。“Class文件”這種特定的二進位文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號... ...
  • [TOC] ## 1. 我以為 我以為 [GoPool](https://github.com/devchat-ai/gopool) 這個項目會曇花一現,從此在 GitHub 上封塵。 > 關於 GoPool 項目誕生的故事:[《僅三天,我用 GPT-4 生成了性能全網第一的 Golang Work ...
  • 最近小組在開展讀書角活動,我們小組選的是《深入理解JVM虛擬機》,相信這本書對於各位程式猿們都不陌生,我也是之前在學校準備面試期間大致讀過一遍,emm時隔多日,對裡面的知識也就模糊了。這次開始的時候從前面的JDK發展史和JVM虛擬機家族著手,之前都是粗略讀過,這次通過查閱相關資料並收集在每一個JDK... ...
  • 作者:老鷹湯 \ 鏈接:https://juejin.cn/post/7156439842958606349 ## 線上事故回顧 前段時間新增一個特別簡單的功能,晚上上線前`review`代碼時想到公司拼搏進取的價值觀臨時加一行log日誌,覺得就一行簡單的日誌基本上沒啥問題,結果剛上完線後一堆報警, ...
  • ## 1、問題 在工作中經常需要在內網環境中安裝python第三方庫,使用從pypi上下載的whl文件來安裝又經常遇到該庫也需要依賴包,以至於並不能成功安裝。 ## 2、解決辦法 (1)查看所需第三方庫安裝是否需要依賴庫(以requests為例) ``` pip show requests ``` ...
  • ## 教程簡介 Lucene是apache軟體基金會 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟體開發人員提供 ...
  • Redis典型的應用場景就是數據緩存能力,用來解決業務中最容易出現的查詢性能問題,提升系統的響應效率;其次就是分散式鎖機制,用來解決分散式系統中多線程併發處理資源的安全問題; ...
  • Java Instrumentation 包 Java Instrumentation 概述 Java Instrumentation 這個技術看起來非常神秘,很少有書會詳細介紹。但是有很多工具是基於 Instrumentation 來實現的: APM 產品: pinpoint、skywalking ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...