1.概述

面向切面編程(Aspect Orient Programming,AOP):是一種編程範式,旨在將 橫切關注點(Cross-Cutting Concerns,如日誌、事務、安全等) 從業務邏輯中分離出來,通過模塊化的方式增強代碼的可維護性和複用性。核心思想是通過“切面”定義通用功能,並在運行時動態織入到目標代碼中

橫切關注點(Cross-Cutting Concerns):指的是在系統中"橫向"跨越多個模塊、多個層次的功能需求,它們無法很好地被封裝在單個類或模塊中

1.1 場景舉例:監控業務性能

1.1.1 硬編碼實現

@Slf4j
public class HardCoding {
    public void demo() {
        long startTime = System.currentTimeMillis();

        //業務代碼

        log.info("消耗時間:{}", System.currentTimeMillis() - startTime);
    }

    public static void main(String[] args) {
        new HardCoding().demo();
    }
}

使用這種硬編碼方式監控業務性能主要有以下缺點:

  • 代碼侵入性強:業務代碼與監控代碼耦合,修改監控代碼會影響業務代碼
  • 重複代碼多:每個方法都要重複編寫監控代碼,維護困難
  • 不利於管理:監控邏輯分散,難以統一管理
  • 容易遺漏:開發人員可能忘記添加監控代碼

1.1.2 AOP實現

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>3.4.8</version> <!-- 應與SpringBoot版本一致 -->
</dependency>
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {

    @RequestMapping("/a")
    public void methodA(){
        log.info("執行methodA");
    }

    @RequestMapping("/b")
    public void methodB(){
        log.info("執行methodB");
    }

    @RequestMapping("/c")
    public void methodC(){
        log.info("執行methodC");
    }
}
@Component
@Slf4j
@Aspect
public class AOP {
    @Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //記錄開始時間
        long startTime = System.currentTimeMillis();
        //執行業務
        Object result = joinPoint.proceed();
        //計算時間
        log.info("消耗時間:{}", System.currentTimeMillis() - startTime);
        return result;
    }
}

執行結果

Spring詳解(五)------AOP_#java

相較於硬編碼的方式,使用Spring AOP監控業務性能有很多顯著好處:

  • 代碼解耦:業務代碼與監控代碼完全分離,易於維護
  • 統一管理:所有監控邏輯集中處理,保持一致性和規範性
  • 無侵入性:對現有代碼零侵入,新增監控不影響業務邏輯
  • 複用性強:一套監控方案可以應用到整個項目中的所有方法

1.2 核心術語

Spring詳解(五)------AOP_#spring boot_02

  • 切入點(Pointcut):提供一個表達式,用於指定對哪些方法進行增強。例如,上圖箭頭切到的ABC業務的集合為一個切入點,具體能切到哪些方法與表達式有關
  • 連接點(Join Point):滿足切點表達式的方法。上圖中處理業務A/B/C的執行前後均為連接點(切入點包含連接點)
  • 通知(Advice):連接點的共性功能(重複的邏輯)。如上圖箭頭執行的邏輯
  • 切面(Aspect):定義了在何處(切入點)和何時(通知)執行額外邏輯,即切入點+通知

2.Spring AOP

2.1 @Aspect

作用:用於標識一個類為切面(Aspect)。切面包含切入點(Pointcut)和通知(Advice),用於模塊化橫切關注點(如日誌、事務、權限等)

Spring詳解(五)------AOP_#spring boot_03

2.2 切入點

execution:是Spring AOP中定義切點的一種表達式,用於指定在哪些方法或類上應用通知(Advice)。它將橫切關注點(如日誌、事務)與業務邏輯分離,通過表達式匹配目標方法或類

//語法結構
execution(<訪問限定修飾符> <返回類型> <包名.類名.方法(方法參數)> <異常>)

*:匹配任意字符(除包分隔符外)
..:匹配任意子包或多級目錄;匹配任意數量參數

@Pointcut:是Spring AOP中的一個註解,用於定義一個可重用的切點表達式

2.3 通知類型

  • ①Around註解·:最強大的通知類型,可以在目標方法執行前後完全控制其行為。它需要接收一個ProceedingJoinPoint類型參數,通過調用proceedingJoinPoint.proceed()來執行目標方法。Around通知可以修改返回值、處理異常或完全阻止目標方法執行
  • ②Before註解:在目標方法執行前觸發,無法阻止方法執行(除非拋出異常)
  • ③After註解:在目標方法完成後執行,無論方法是正常返回還是拋出異常
  • ④AfterReturning註解:在目標方法正常返回後執行,可以通過訪問(不能修改)返回值
  • returning屬性:指定接收返回值的參數名,參數類型必須與目標方法返回類型兼容
  • ⑤AfterThrowing註解:只在目標方法拋出異常時執行
  • throwing屬性:用於綁定目標方法拋出的異常對象
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {

    @RequestMapping("/a")
    public Object methodA(Integer id){
        log.info("執行methodA");
        return id;
    }

    @RequestMapping("/b")
    public void methodB(){
        log.info("執行methodB");
        throw new RuntimeException("發生異常");
    }
}
@Component
@Slf4j
@Aspect
public class AOP {

    @Pointcut("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround:業務執行前");
        //執行業務
        Object result = joinPoint.proceed();
        log.info("doAround:業務執行後,result:{}", result);
        return "success";
    }

    @Before("pointcut()")
    //@Before("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doBefore(){
        log.info("doBefore");
    }

    @After("pointcut()")
    //@After("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfter(){
        log.info("doAfter");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    //@AfterReturning("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfterReturning(Integer id){
        //發生異常時不執行
        log.info("doAfterReturning,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    //@AfterThrowing("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing,throwable:{}", throwable.getMessage());
    }
}
  • 1.無異常拋出時,URL:127.0.0.1:8080/demo/a?id=1
  • Spring詳解(五)------AOP_#spring boot_04


  • Spring詳解(五)------AOP_#spring boot_05


  • 2.有異常拋出時,URL:127.0.0.1:8080/demo/b
  • Spring詳解(五)------AOP_#java_06


  • Spring詳解(五)------AOP_#aop_07


Spring詳解(五)------AOP_#spring boot_08

2.4 @annotation

作用:@annotation表達式用於匹配帶有指定註解的方法,是AOP中實現精準切入的關鍵方式之一。通過該表達式,可以攔截被特定註解標記的方法,實現邏輯增強

1.創建自定義註解

@Target(ElementType.METHOD)//註解級別:方法註解
@Retention(RetentionPolicy.RUNTIME)//生命週期:運行時
public @interface CustomizeAspect {

}

2.使用@annotation表達式描述切點

//添加到切面類中
@Around("@annotation(org.example.springaop.config.CustomizeAspect)")
public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("customize before");
    Object result = joinPoint.proceed();
    log.info("customize after,result:{}", result);
    return "success";
}

3.將自定義註解添加到連接點上

//添加到Controller類中
@RequestMapping("/c")
@CustomizeAspect
public Object methodC(Integer id){
    log.info("執行methodC");
    return id;
}
執行結果

Spring詳解(五)------AOP_#aop_09


Spring詳解(五)------AOP_#aop_10

2.5 @Order

作用:用於指定Bean的加載順序,其核心作用是通過數值定義優先級,數值越小優先級越高

@Component
@Slf4j
@Aspect
@Order(1)
public class AOP1 {

    @Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.controller.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround1:業務執行前");
        //執行業務
        Object result = joinPoint.proceed();
        //計算時間
        log.info("doAround1:業務執行後,result:{}", result);
        return "success1";
    }

    @Before("pointcut()")
    public void doBefore(){
        log.info("doBefore1");
    }

    @After("pointcut()")
    public void doAfter(){
        log.info("doAfter1");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    public void doAfterReturning(Integer id){
        //發生異常時不執行
        log.info("doAfterReturning1,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing1,throwable:{}", throwable.getMessage());
    }

    @Around("@annotation(org.example.springaop.config.CustomizeAspect)")
    public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("customize before1");
        Object result = joinPoint.proceed();
        log.info("customize after1,result:{}", result);
        return "success1";
    }
}
@Component
@Slf4j
@Aspect
@Order(2)
public class AOP2 {

    @Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.controller.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround2:業務執行前");
        //執行業務
        Object result = joinPoint.proceed();
        //計算時間
        log.info("doAround2:業務執行後,result:{}", result);
        return "success2";
    }

    @Before("pointcut()")
    public void doBefore(){
        log.info("doBefore2");
    }

    @After("pointcut()")
    public void doAfter(){
        log.info("doAfter2");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    public void doAfterReturning(Integer id){
        //發生異常時不執行
        log.info("doAfterReturning2,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing2,throwable:{}", throwable.getMessage());
    }

    @Around("@annotation(org.example.springaop.config.CustomizeAspect)")
    public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("customize before2");
        Object result = joinPoint.proceed();
        log.info("customize after2,result:{}", result);
        return "success2";
    }
}
執行結果

Spring詳解(五)------AOP_spring_11


Spring詳解(五)------AOP_spring_12

Spring詳解(五)------AOP_spring_13

3.AOP底層原理

3.1 代理模式

代理模式(Proxy Pattern)通過創建一個代理對象來控制對目標對象的訪問。代理對象作為目標對象的替代品,可以在訪問前後添加額外邏輯(如權限控制、性能監控等)

Spring詳解(五)------AOP_#代理模式_14

3.2 靜態代理

代理類在編譯期確定,需要手動為每個目標類編寫代理類

以租房為例:租客(調用方)、房東(目標對象)、中介(代理對象)

public interface IHouse {
    void rent();
    void sell();
}
//房東
public class RealHouse implements IHouse {
    @Override
    public void rent() {
        System.out.println("房東出租房子");
    }

    @Override
    public void sell() {
        System.out.println("房東售賣房子");
    }
}
//中介
public class HouseProxy implements IHouse {

    private final IHouse realHouse;

    public HouseProxy(IHouse realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    public void rent() {
        System.out.println("開始代理");
        realHouse.rent();
        System.out.println("結束代理");
    }

    @Override
    public void sell() {
        System.out.println("開始代理");
        realHouse.sell();
        System.out.println("結束代理");
    }

    public static void main(String[] args) {
        IHouse iHouse = new HouseProxy(new RealHouse());
        iHouse.rent();
        iHouse.sell();
    }
}

執行結果
開始代理
房東出租房子
結束代理
開始代理
房東售賣房子
結束代理

3.3 動態代理

AOP的底層原理依賴於動態代理:不需要針對每一個目標對象創建一個代理對象,而是將代理對象的創建時機推遲到程序運行時交由JVM完成

3.3.1 JDK動態代理

JDK動態代理是Java標準庫提供的方式,要求目標對象必須實現接口。通過java.lang.reflect.Proxy類java.lang.reflect.InvocationHandler接口動態生成代理類,生成的代理對象和目標對象實現自同一接口(而非與目標類本身有直接繼承關係),與上述靜態代理類似,但把代理對象的生成時機推遲到程序運行時

Proxy.newProxyInstance()是Java動態代理的核心方法,用於在運行時創建代理對象。該方法接收三個參數:

  • ClassLoader loader:用於加載代理類的類加載器。通常傳入目標類的類加載器,確保代理類與目標類在同一個類加載器環境中
    • 代理對象需要實現目標接口,其字節碼由Proxy工具類動態生成。通過目標類加載器創建代理類,可保證類型系統的一致性
  • Class<?>[] interfaces:目標對象實現的接口數組。代理類會實現這些接口,並將方法調用轉發到InvocationHandler
  • InvocationHandler h:負責處理代理對象的方法調用
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {

    private final Object realHouse;
    public JDKInvocationHandler(Object realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    //Object proxy:生成的代理對象實例,Method method:被調用的目標方法對象,Object[] args:調用目標方法時傳入的參數數組
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("JDK開始代理");
        //調用realHouse的方法
        Object invoke = method.invoke(realHouse, args);
        log.info("JDK結束代理,invoke:{}", invoke);
        return invoke;
    }
}
public class Main {
    public static void main(String[] args) {
        IHouse target = new RealHouse();
        //JDK動態代理,只能代理接口
        IHouse iHouse = (IHouse)Proxy.newProxyInstance(
                RealHouse.class.getClassLoader(),
                new Class[]{IHouse.class},
                new JDKInvocationHandler(target)
        );
        iHouse.rent();
        iHouse.sell();
    }
}
運行結果

Spring詳解(五)------AOP_#spring boot_15

3.3.2 CGLIB動態代理

CGLIB(Code Generation Library)是第三方庫,基於字節碼增強(動態生成目標類的子類字節碼,重寫方法邏輯),通過繼承方式實現代理,不要求目標對象實現接口。通過org.springframework.cglib.proxy.Enhancer類動態生成目標類的子類作為代理

Spring詳解(五)------AOP_#spring boot_16

//interface MethodInterceptor extends Callback
@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {

    private final Object realHouse;

    public CGLibMethodInterceptor(Object realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    //Object obj:動態生成的代理對象實例,Method method:當前被攔截的目標方法對象,Object[] args:方法調用時傳入的參數數組,MethodProxy proxy:方法代理對象
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        log.info("開始代理");
        IHouse result = (IHouse)method.invoke(realHouse, args);
        log.info("結束代理");
        return result;
    }
}
public class Main {

    public static void main(String[] args) {
        RealHouse target = new RealHouse();
        RealHouse realHouse = (RealHouse)Enhancer.create(
                RealHouse.class,
                new CGLibMethodInterceptor(target)
        );
        realHouse.rent();
        realHouse.sell();
    }
}
執行結果

Spring詳解(五)------AOP_#代理模式_17