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
代替内联表达式?
-
减少重复:避免在多处重复相同的表达式。
-
易于维护:规则集中管理,修改时只需改一处。
-
代码简洁:通知方法只需关注逻辑,无需关注复杂表达式。
AOP切入点表达式:(略)
二 AOP的底层核心原理
1. 动态代理(Dynamic Proxy)
-
目标:为原始对象创建一个代理对象,在调用目标方法时插入增强逻辑。
-
实现方式:
-
JDK动态代理:基于接口生成代理对象(要求目标类必须实现接口)。
-
CGLIB代理:通过继承目标类生成子类代理(适用于无接口的类)。
-
-
关键流程:
-
代理对象拦截方法调用。
-
根据配置的切面规则,生成增强器链。
-
按顺序执行增强逻辑(前置、环绕、后置等)。
-
最终调用原始方法或返回结果。
-
2. 字节码增强(Bytecode Manipulation)
-
目标:在编译期或类加载期直接修改类的字节码,插入增强逻辑。
-
实现方式:
-
编译时织入(如AspectJ):在源代码编译阶段修改字节码。
-
加载时织入(LTW):在类加载到JVM时动态修改字节码。
-
-
优势:无需代理对象,性能更高,支持更复杂的切面逻辑(如对静态方法、构造函数的增强)。
增强器链的作用与定位
增强器链是动态代理运行时的产物,用于管理多个增强逻辑的执行顺序,但它只是AOP底层实现中的中间层机制,而非最底层原理。例如:
-
代理对象生成(动态代理或字节码增强)是底层基础。
-
增强器链是代理对象在运行时组织多个增强逻辑的流程控制器。
-
最终执行通过反射调用原始方法。
AOP实现的分层模型
-
底层:动态代理或字节码增强(生成代理类或修改原始类)。
-
中间层:增强器链(协调多个增强逻辑的执行顺序)。
-
上层:用户定义的切面(@Aspect)和增强(@Before、@Around等)。
不同框架的底层实现差异
-
Spring AOP:
-
基于动态代理(JDK/CGLIB)。
-
仅支持方法级别的增强,依赖Spring容器。
-
增强器链在代理对象中通过
ReflectiveMethodInvocation
类实现。
-
-
AspectJ:
-
基于字节码增强(编译时或加载时)。
-
支持更细粒度的连接点(如字段访问、静态代码块)。
-
无需代理对象,直接修改目标类。
-
关键区别:代理 vs 字节码增强
特性 | 动态代理 | 字节码增强 |
---|---|---|
性能 | 运行时反射调用,略低 | 直接修改字节码,性能更高 |
适用范围 | 仅方法级别,需接口(JDK代理) | 支持字段、构造方法等更广泛场景 |
依赖 | 需代理对象 | 无需代理,直接修改目标类 |
框架示例 | Spring AOP | AspectJ |
总结
-
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 中功能最强大的通知类型,它能够完全控制目标方法的执行流程,允许在目标方法执行前后插入自定义逻辑,并决定是否继续执行目标方法。
核心特性:
-
包裹目标方法
环绕通知像“包装纸”一样包裹目标方法,可以在以下节点插入逻辑:-
目标方法执行前(预处理,如权限校验)。
-
目标方法执行后(后处理,如日志记录)。
-
目标方法抛出异常时(异常处理,如事务回滚)。
-
-
完全控制流程
-
需显式调用
proceed()
触发目标方法及后续通知,否则目标方法不会执行。 -
可以修改目标方法的参数、返回值,甚至阻止其执行。
-
-
唯一能直接操作目标方法返回值
其他通知(如@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;
}
}
运行结果