当前位置: 首页 > article >正文

Spring(九)AOP-底层原理与通知方法的理解

目录

一 区分@Pointcut与@After、Before等通知方法

1. @Pointcut 定义公共切入点表达式

2. @Before 通知引用公共切入点

3. @After 通知直接内联切入点表达式

为什么推荐用 @Pointcut 代替内联表达式?

二 AOP的底层核心原理

1. 动态代理(Dynamic Proxy)

2. 字节码增强(Bytecode Manipulation)

增强器链的作用与定位

AOP实现的分层模型

不同框架的底层实现差异

关键区别:代理 vs 字节码增强

总结

三 通知方法的执行顺序 

1 正常链路:前置通知->目标方法->返回通知->后置通知

2 异常链路:前置通知->目标方法->异常通知->后置通知

四 JoinPoint连接点信息日志切面

1 初始版本:

2 改进版本:

五 多切面执行顺序@Order(数字越小越先执行)


一 区分@Pointcut与@After、Before等通知方法

@Pointcut提前指定,使用时直接调用,@After是根据情况每一次都要写对应的值

1. @Pointcut 定义公共切入点表达式

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
  • 作用:就像定义一条规则,告诉AOP“哪些方法需要被拦截”。

  • 特点

    • 这条规则可以重复使用,避免重复写同样的拦截条件。

    • 例如:拦截com.example.service包下所有类的所有方法。

2. @Before 通知引用公共切入点

@Before("serviceMethods()")
public void logBefore() {
    System.out.println("方法执行前");
}
  • 作用:在目标方法执行前,执行打印“方法执行前”的操作。

  • 关键点

    • serviceMethods():直接引用之前定义的规则(@Pointcut)。

    • 相当于说:“按照这条规则拦截方法,并在它们执行前打印日志”。

3. @After 通知直接内联切入点表达式

@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
    System.out.println("方法执行后");
}
  • 作用:在目标方法执行后(无论是否成功),执行打印“方法执行后”的操作。

  • 关键点

    • execution(...):直接写了一条新的拦截规则(和serviceMethods()内容相同)。

    • 相当于说:“拦截com.example.service包下所有方法,并在它们执行后打印日志”。

为什么推荐用 @Pointcut 代替内联表达式?

  1. 减少重复:避免在多处重复相同的表达式。

  2. 易于维护:规则集中管理,修改时只需改一处。

  3. 代码简洁:通知方法只需关注逻辑,无需关注复杂表达式。

AOP切入点表达式:(略)

二 AOP的底层核心原理

1. 动态代理(Dynamic Proxy)
  • 目标:为原始对象创建一个代理对象,在调用目标方法时插入增强逻辑。

  • 实现方式

    • JDK动态代理:基于接口生成代理对象(要求目标类必须实现接口)。

    • CGLIB代理:通过继承目标类生成子类代理(适用于无接口的类)。

  • 关键流程

    1. 代理对象拦截方法调用。

    2. 根据配置的切面规则,生成增强器链。

    3. 按顺序执行增强逻辑(前置、环绕、后置等)。

    4. 最终调用原始方法或返回结果。

2. 字节码增强(Bytecode Manipulation)
  • 目标:在编译期或类加载期直接修改类的字节码,插入增强逻辑。

  • 实现方式

    • 编译时织入(如AspectJ):在源代码编译阶段修改字节码。

    • 加载时织入(LTW):在类加载到JVM时动态修改字节码。

  • 优势:无需代理对象,性能更高,支持更复杂的切面逻辑(如对静态方法、构造函数的增强)。


增强器链的作用与定位

增强器链是动态代理运行时的产物,用于管理多个增强逻辑的执行顺序,但它只是AOP底层实现中的中间层机制,而非最底层原理。例如:

  1. 代理对象生成(动态代理或字节码增强)是底层基础。

  2. 增强器链是代理对象在运行时组织多个增强逻辑的流程控制器。

  3. 最终执行通过反射调用原始方法。


AOP实现的分层模型

  1. 底层:动态代理或字节码增强(生成代理类或修改原始类)。

  2. 中间层:增强器链(协调多个增强逻辑的执行顺序)。

  3. 上层:用户定义的切面(@Aspect)和增强(@Before、@Around等)。


不同框架的底层实现差异

  1. Spring AOP

    • 基于动态代理(JDK/CGLIB)。

    • 仅支持方法级别的增强,依赖Spring容器。

    • 增强器链在代理对象中通过ReflectiveMethodInvocation类实现。

  2. AspectJ

    • 基于字节码增强(编译时或加载时)。

    • 支持更细粒度的连接点(如字段访问、静态代码块)。

    • 无需代理对象,直接修改目标类。


关键区别:代理 vs 字节码增强

特性动态代理字节码增强
性能运行时反射调用,略低直接修改字节码,性能更高
适用范围仅方法级别,需接口(JDK代理)支持字段、构造方法等更广泛场景
依赖需代理对象无需代理,直接修改目标类
框架示例Spring AOPAspectJ

总结

  • AOP的底层原理是动态代理或字节码增强技术,用于生成代理类或直接修改目标类。

  • 增强器链是运行时的执行流程控制机制,用于协调多个增强逻辑的顺序和协作。

  • 不同框架(如Spring AOP和AspectJ)的底层实现差异显著,但最终目标一致:无侵入地分离横切关注点

三 通知方法的执行顺序 

1 正常链路:前置通知->目标方法->返回通知->后置通知

2 异常链路:前置通知->目标方法->异常通知->后置通知

四 JoinPoint连接点信息日志切面

在面向切面编程(AOP)中,JoinPoint 是用于在通知(Advice)中获取被拦截方法上下文信息的关键对象。通过 JoinPoint,你可以访问目标方法的参数、方法签名、目标对象等。以下是 JoinPoint 在通知方法中传递参数的具体使用方法和示例:

1 初始版本:
 

package org.example.spring02.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class LogAspect {
    

    @Before("execution(int org.example.spring02.MathMethod.MathCalculator.*(..))")
    public void logStart(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名+参数
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("【切面-日志】方法名是:" + methodName + " 参数是:" + Arrays.toString(args) + ". 前置执行");

    }


    @After("execution(int *(int,int))")
    public void logEnd(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + ", 后置执行");
    }


    @AfterReturning(value = "execution(int org.example.spring02.MathMethod.MathCalculator.*(..))",
            returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + " 返回值是:" + result);

    }

    @AfterThrowing(value = "execution(int org.example.spring02.MathMethod.MathCalculator.*(..))",
            throwing = "e"
    )
    public void logException(JoinPoint joinPoint, Exception e) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();

        System.out.println("【切面-日志】方法名为" + methodName + "方法异常:" + e.getMessage());
    }


}

2 改进版本:

package org.example.spring02.MathMethod;

public interface MathCalculator {

    //定义四则运算
    public int div(int a, int b);

    public int mul(int a, int b);

    public int sub(int a, int b);

    public int add(int a, int b);

}
package org.example.spring02.MathMethod.MIm;

import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;

@Component
public class MathCalculatorImpl implements MathCalculator {
    @Override
    public int add(int a, int b) {
        System.out.println(" add被调用了");
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println(" sub被调用了");
        return a - b;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println(" mul被调用了");
        return a * b;
    }

    @Override
    public int div(int a, int b) {
        System.out.println(" div被调用了");
        return a / b;
    }

}

package org.example.spring02.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class LogAspect {

    @Pointcut("execution(int org.example.spring02.MathMethod.MathCalculator.*(..))")
    public void pointCutMethod() {
    }

    @Before("pointCutMethod()")
    public void logStart(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名+参数
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("【切面-日志】方法名是:" + methodName + " 参数是:" + Arrays.toString(args) + ". 前置执行");

    }


    @After("pointCutMethod()")
    public void logEnd(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + ", 后置执行");
    }


    @AfterReturning(value = "pointCutMethod()",
            returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + " 返回值是:" + result);

    }

    @AfterThrowing(value = "pointCutMethod()",
            throwing = "e"
    )
    public void logException(JoinPoint joinPoint, Exception e) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();

        System.out.println("【切面-日志】方法名为" + methodName + "方法异常:" + e.getMessage());
    }


}
package org.example.spring02;

import org.example.spring02.MathMethod.MathCalculator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class AopTest {

    //容器中是代理对象
    @Autowired
    MathCalculator mc;

    @Test
    public void test01() {
        int add = mc.div(2, 1);
        System.out.println("结果" + add);
    }

}

运行结果:

五 多切面执行顺序@Order(数字越小越先执行)

package org.example.spring02.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Order(2)
@Component
@Aspect
public class AuthAspect {
    @Pointcut("execution(int org.example.spring02.MathMethod.MathCalculator.*(..))")
    public void pointCutMethod() {
    }


    @Before("pointCutMethod()")
    public void logStart(JoinPoint joinPoint) {
        System.out.println("【权限】前置");
    }


    @After("pointCutMethod()")
    public void logEnd(JoinPoint joinPoint) {
        System.out.println("【权限】后置");
    }


    @AfterReturning(value = "pointCutMethod()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("【权限】返回");
    }

    @AfterThrowing(value = "pointCutMethod()", throwing = "e"
    )
    public void logException(JoinPoint joinPoint, Exception e) {
        System.out.println("【权限】异常");
    }


}
package org.example.spring02.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Order(1)
@Component
@Aspect
public class LogAspect {

    @Pointcut("execution(int org.example.spring02.MathMethod.MathCalculator.*(..))")
    public void pointCutMethod() {
    }

    @Before("pointCutMethod()")
    public void logStart(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名+参数
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("【切面-日志】方法名是:" + methodName + " 参数是:" + Arrays.toString(args) + ". 前置执行");

    }


    @After("pointCutMethod()")
    public void logEnd(JoinPoint joinPoint) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + ", 后置执行");
    }


    @AfterReturning(value = "pointCutMethod()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();
        System.out.println("【切面-日志】方法名为:" + methodName + " 返回值是:" + result);

    }

    @AfterThrowing(value = "pointCutMethod()", throwing = "e")
    public void logException(JoinPoint joinPoint, Exception e) {
        //拿到方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法名
        String methodName = signature.getName();

        System.out.println("【切面-日志】方法名为" + methodName + "方法异常:" + e.getMessage());
    }


}

执行结果

六 补充:环绕通知

环绕通知(@Around) 是 AOP 中功能最强大的通知类型,它能够完全控制目标方法的执行流程,允许在目标方法执行前后插入自定义逻辑,并决定是否继续执行目标方法。

核心特性:

  1. 包裹目标方法
    环绕通知像“包装纸”一样包裹目标方法,可以在以下节点插入逻辑:

    • 目标方法执行前(预处理,如权限校验)。

    • 目标方法执行后(后处理,如日志记录)。

    • 目标方法抛出异常时(异常处理,如事务回滚)。

  2. 完全控制流程

    • 需显式调用 proceed() 触发目标方法及后续通知,否则目标方法不会执行。

    • 可以修改目标方法的参数、返回值,甚至阻止其执行。

  3. 唯一能直接操作目标方法返回值
    其他通知(如 @AfterReturning)只能读取返回值,而环绕通知可以修改或替换返回值。

初始版本:

package org.example.spring02.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Order(3)
@Component
@Aspect
public class AroundAspect {

    @Pointcut("execution(int org.example.spring02.mathmethod.MathCalculator.*(..))")
    public void pointCutMethod() {
    }

    @Around(value = "pointCutMethod()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标方法的参数
        Object[] args = pjp.getArgs();
        System.out.println("【环绕】- 前置通知" + Arrays.toString(args));
        Object proceed = null;
        try {
            //继续执行目标方法,反射method.invoke()->接收传入参数的proceed,实现修改目标方法执行使用的参数
            proceed = pjp.proceed(args);
            System.out.println("【环绕】- 返回通知" + proceed);
        } catch (Exception e) {
            System.out.println("【环绕】- 异常通知" + e.getMessage());
        } finally {
            System.out.println("【环绕】- 后置通知");
        }

        return proceed;
    }


}

异常处理不完善

问题
在 catch 块中捕获异常后未重新抛出,导致异常被吞没,上层调用方无法感知异常。
影响

  • 业务逻辑可能因异常未传播而出现数据不一致(如事务未回滚)。

改进版本:

package org.example.spring02.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Order(3)
@Component
@Aspect
public class AroundAspect {

    @Pointcut("execution(int org.example.spring02.mathmethod.MathCalculator.*(..))")
    public void pointCutMethod() {
    }

    @Around(value = "pointCutMethod()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标方法的参数
        Object[] args = pjp.getArgs();
        System.out.println("【环绕】- 前置通知" + Arrays.toString(args));
        Object proceed = null;
        try {
            //继续执行目标方法,反射method.invoke()->接收传入参数的proceed,实现修改目标方法执行使用的参数
            proceed = pjp.proceed(args);
            System.out.println("【环绕】- 返回通知" + proceed);
        } catch (Exception e) {
            System.out.println("【环绕】- 异常通知" + e.getMessage());
            //将异常抛出
            throw e;
        } finally {
            System.out.println("【环绕】- 后置通知");
        }

        return proceed;
    }


}

运行结果


http://www.kler.cn/a/582164.html

相关文章:

  • SQL Server 与 MySQL 的库同步工具推荐
  • 《React 属性与状态江湖:从验证到表单受控的实战探险》
  • 【从零开始学习计算机科学】计算机体系结构(二)指令级并行(ILP)
  • 医疗AI测试实战:如何确保人工智能安全赋能医疗行业?
  • 【C#学习笔记01】从程序框架到函数调用
  • 【AI大模型智能应用】Deepseek生成测试用例
  • 2025,以SBOM为基础的云原生应用安全治理
  • Linux学习(十五)(故障排除(ICMP,Ping,Traceroute,网络统计,数据包分析))
  • 不同AI生成的PHP版雪花算法
  • 装饰器模式的C++实现示例
  • 数据分析绘制随时间顺序变化图加入线性趋势线——numpy库的polyfit计算一次多项式拟合
  • 揭开AI-OPS 的神秘面纱 第五讲 AI 模型服务层(开源方向)
  • electron+vue+webview内嵌网页并注入js
  • 【实战ES】实战 Elasticsearch:快速上手与深度实践-6.1.1RBAC角色权限设计
  • 【Linux跬步积累】—— 网络基础
  • Windows系统编程项目(四)窗口管理器
  • 关于Go中使用goroutine协程实现的算法
  • java的字符串,数组,集合的长度/大小
  • 格雷希尔: G80P系列在制动卡钳行业自动化应用
  • 数据结构--【顺序表与链表】笔记