@AspectJ 注解驱动 - 通知类型、Pointcut 指示器与切面排序
Spring AOP 自 2.0 版本起全面支持 @AspectJ 注解风格的切面编程。通过引入 @Aspect、@Before、@Around 等注解,开发者可以告别臃肿的 XML 配置,以纯 Java 方式声明切面。本文深入剖析 @AspectJ 注解驱动的核心技术细节,涵盖五种通知类型、九种 Pointcut 指示器、通知执行顺序、多切面排序机制、JoinPoint API,以及 Spring 框架源码级别的切面解析与排序流程。
本文所有源码分析基于 Spring Framework 5.3.x 版本。
一、@AspectJ 注解驱动概述
@AspectJ 是 AspectJ 项目提供的一套注解体系,Spring AOP 将其引入后,允许开发者通过注解声明切面,而无需依赖 AspectJ 编译器或织入器。核心注解如下:
| 注解 | 用途 |
|---|---|
@Aspect | 声明一个类为切面 |
@Pointcut | 声明切入点表达式 |
@Before | 前置通知 |
@After | 最终通知(类似 finally) |
@AfterReturning | 返回通知 |
@AfterThrowing | 异常通知 |
@Around | 环绕通知 |
@Order | 切面排序 |
@DeclareParents | 引入增强(本文不展开) |
启用 @AspectJ 驱动的两种方式:
// 方式一:Java 配置(推荐)
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
// 方式二:XML 配置
<aop:aspectj-autoproxy/>二、五种通知类型详解
2.1 @Before —— 前置通知
在目标方法执行之前执行。前置通知不能阻止目标方法的执行(除非抛出异常)。
@Component
@Aspect
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("[@Before] 准备执行方法: " + methodName);
}
}特点:
- 可访问
JoinPoint获取方法签名、参数等信息。 - 无法修改目标方法的入参(除非使用
@Around)。
2.2 @After —— 最终通知
在目标方法执行结束之后执行(无论正常返回还是抛出异常),类似于 finally 块。
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint jp) {
System.out.println("[@After] 方法执行完毕: " + jp.getSignature().getName());
}特点:
- 无法获取方法的返回值或抛出的异常。
- 在
@After中抛出异常会覆盖原始异常,需谨慎处理。
2.3 @AfterReturning —— 返回通知
在目标方法正常返回之后执行。可以访问方法的返回值。
@AfterReturning(
value = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void afterReturningAdvice(JoinPoint jp, Object result) {
System.out.println("[@AfterReturning] 方法: " + jp.getSignature().getName()
+ ", 返回值: " + result);
}关键点:
returning属性指定绑定返回值的参数名,必须与通知方法参数名一致。- 如果通知方法中修改了
result参数(仅当类型为引用类型时),对于引用类型返回值,无法通过修改参数改变实际返回值;但对于Object类型返回值且returning类型为Object时,Spring AOP 不允许修改返回值。
2.4 @AfterThrowing —— 异常通知
在目标方法抛出异常之后执行。可以访问抛出的异常对象。
@AfterThrowing(
value = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void afterThrowingAdvice(JoinPoint jp, Throwable ex) {
System.out.println("[@AfterThrowing] 方法: " + jp.getSignature().getName()
+ ", 异常: " + ex.getMessage());
}关键点:
throwing属性指定绑定异常的参数名。- 异常通知无法阻止异常的传播;若想在通知中处理异常并让程序继续执行,应使用
@Around。
2.5 @Around —— 环绕通知
最强大的通知类型,可以在目标方法执行前后自定义处理逻辑,并控制目标方法是否执行以及修改返回值或抛出异常。
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
long start = System.currentTimeMillis();
try {
// 前置逻辑
System.out.println("[@Around] 方法开始: " + methodName);
// 执行目标方法
Object result = pjp.proceed();
// 后置逻辑
System.out.println("[@Around] 方法结束: " + methodName);
return result;
} catch (Exception e) {
System.out.println("[@Around] 异常: " + e.getMessage());
throw e; // 重新抛出
} finally {
long elapsed = System.currentTimeMillis() - start;
System.out.println("[@Around] 耗时: " + elapsed + "ms");
}
}关键点:
ProceedingJoinPoint.proceed()是执行目标方法的唯一入口。- 不调用
proceed()则目标方法不会执行。 - 可以修改
proceed()的参数来改变目标方法的入参值。 - 可以修改返回值或抛出自定义异常。
变体:修改目标方法参数
@Around("execution(* com.example.service.PaymentService.transfer(..))")
public Object modifyArgs(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 将金额放大 10 倍(仅演示,勿用于生产)
if (args.length > 0 && args[0] instanceof BigDecimal) {
args[0] = ((BigDecimal) args[0]).multiply(BigDecimal.TEN);
}
return pjp.proceed(args);
}三、@Pointcut 与九种指示器
3.1 声明 Pointcut
@Pointcut 注解用于声明一个可复用的切入点表达式,之后可以在通知注解中直接引用方法名。
@Component
@Aspect
public class CommonPointcuts {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("within(com.example.service..*)")
public void serviceWithin() {}
// 组合切入点
@Pointcut("serviceLayer() && !within(com.example.service.internal..*)")
public void publicServiceLayer() {}
}引用方式:
@Before("CommonPointcuts.serviceLayer()")
public void beforeService(JoinPoint jp) {
// ...
}3.2 九种 Pointcut 指示器详解
Spring AOP 支持以下 9 种 AspectJ 切入点指示器:
| 指示器 | 作用 | 示例 |
|---|---|---|
execution | 匹配方法执行连接点 | execution(public * com.example..*.*(..)) |
within | 匹配指定类型内的连接点 | within(com.example.service.*) |
this | 匹配 AOP 代理对象为指定类型的连接点 | this(com.example.service.UserService) |
target | 匹配目标对象为指定类型的连接点 | target(com.example.service.UserService) |
args | 匹配方法参数为指定类型的连接点 | args(String, int) |
@within | 匹配声明了指定注解的类型内的连接点 | @within(org.springframework.stereotype.Service) |
@target | 匹配目标对象声明了指定注解的连接点 | @target(org.springframework.transaction.annotation.Transactional) |
@args | 匹配运行时参数声明了指定注解的连接点 | @args(com.example.annotation.Validated) |
@annotation | 匹配连接点上标注了指定注解的连接点 | @annotation(com.example.annotation.Log) |
3.2.1 execution —— 方法执行匹配
最常用、表达能力最强的指示器。语法格式:
execution([修饰符模式] [返回类型模式] [包名模式].[类名模式].[方法名模式]([参数模式]) [throws 异常模式])// 匹配所有 public 方法
@Pointcut("execution(public * *(..))")
// 匹配 com.example.service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配 com.example.service 包及其子包下所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
// 匹配返回值为 String 且方法名为 find* 的方法
@Pointcut("execution(String com.example..find*(..))")
// 匹配无参方法
@Pointcut("execution(* *(..))")
// 匹配只有一个 String 参数的方法
@Pointcut("execution(* *(String))")3.2.2 within —— 类型内匹配
匹配指定类型内部的所有连接点(即该类中声明的所有方法)。
// 匹配 com.example.service 包中所有类
@Pointcut("within(com.example.service.*)")
// 匹配 com.example.service 包及其子包中所有类
@Pointcut("within(com.example.service..*)")
// 匹配标注了 @Service 的类
@Pointcut("within(@org.springframework.stereotype.Service *)")within 与 execution 的主要区别:within 仅匹配类型级别,不能匹配具体方法签名;execution 则可以精确匹配到方法级别。
3.2.3 this —— 代理对象匹配
匹配 AOP 创建的代理对象是给定类型的实例。
// 代理对象实现了 UserService 接口
@Pointcut("this(com.example.service.UserService)")注意: this 匹配的是 代理对象 的类型。如果目标类实现了多个接口,Spring 默认使用 JDK 动态代理(代理对象类型为接口类型),因此 this 匹配的是接口。若使用 CGLIB 代理,则 this 匹配的是目标类类型。
3.2.4 target —— 目标对象匹配
匹配目标对象是给定类型的实例。
// 目标对象实现了 UserService 接口
@Pointcut("target(com.example.service.UserService)")this vs target 的区别:
| 场景 | this(SomeInterface) | target(SomeInterface) |
|---|---|---|
| JDK 动态代理 | ✅ 匹配 | ✅ 匹配 |
| CGLIB 代理(目标类实现接口) | ✅ 匹配(代理对象实现接口) | ✅ 匹配 |
| CGLIB 代理(目标类未实现接口) | ❌ 不匹配(代理对象是子类) | ✅ 匹配 |
3.2.5 args —— 参数匹配
匹配方法运行时参数类型满足条件的连接点。
// 匹配只有一个参数且为 String 的方法
@Pointcut("args(String)")
// 匹配参数个数 >= 1 且第一个参数为 String 的方法
@Pointcut("args(String, ..)")
// 匹配两个参数且分别为 String 和 int 的方法
@Pointcut("args(String, int)")args 与 execution 中参数模式的关键区别:execution 匹配的是方法签名中声明的参数类型,而 args 匹配的是运行时实际传入的参数类型。例如:
// execution — 签名必须为 Object 类型
@Pointcut("execution(* *(Object))") // 仅匹配声明参数为 Object 的方法
// args — 运行时参数是 Object 即可
@Pointcut("args(Object)") // 匹配运行时传入 Object 参数的方法3.2.6 @within —— 类注解匹配
匹配声明了指定注解的类中的所有连接点。
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceBeanMethods() {}@Service // 这个类被匹配
public class UserService {
public void doSomething() {} // 被匹配
}3.2.7 @target —— 目标对象注解匹配
匹配目标对象(而不是代理对象)声明了指定注解的连接点。
@Pointcut("@target(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}@within 与 @target 在大多数场景下行为一致。细微区别在于:@within 基于声明类型的注解,@target 基于运行时对象类型的注解。当存在继承/代理场景时表现不同。
3.2.8 @args —— 参数注解匹配
匹配运行时参数上标注了指定注解的连接点。
// 匹配参数上标注了 @Valid 注解的方法
@Pointcut("@args(javax.validation.Valid)")
public void validatedArgs() {}public void saveUser(@Valid User user) {} // 被匹配3.2.9 @annotation —— 方法注解匹配
匹配连接点上标注了指定注解的方法。
@Pointcut("@annotation(com.example.annotation.Log)")
public void logAnnotatedMethods() {}public class UserService {
@Log // 这个方法被匹配
public void createUser(User user) {}
}这是实际项目中最常用的指示器之一,配合自定义注解可以实现高度灵活的 AOP 拦截。
3.3 Pointcut 表达式组合
切入点表达式支持逻辑运算符组合:
| 运算符 | 含义 | 示例 |
|---|---|---|
&& | 且 | execution(* *(..)) && @annotation(Log) |
|| | 或 | @within(Service) || @within(Repository) |
! | 非 | execution(* *(..)) && !within(com.example.internal..*) |
@Pointcut("execution(* com.example.service..*(..)) "
+ "&& (@annotation(com.example.annotation.Log) "
+ "|| @annotation(com.example.annotation.Audit))")
public void logOrAuditMethods() {}四、通知执行顺序
4.1 正常情况(无异常)
当目标方法正常执行时,通知执行顺序如下:
@Around 前置代码
↓
@Before
↓
目标方法执行
↓
@AfterReturning
↓
@After
↓
@Around 后置代码完整执行流程示例:
@Component
@Aspect
public class OrderAspect {
@Around("execution(* com.example..doSomething(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("1. @Around 前置");
Object result = pjp.proceed();
System.out.println("5. @Around 后置");
return result;
}
@Before("execution(* com.example..doSomething(..))")
public void before() {
System.out.println("2. @Before");
}
@AfterReturning("execution(* com.example..doSomething(..))")
public void afterReturning() {
System.out.println("3. @AfterReturning");
}
@After("execution(* com.example..doSomething(..))")
public void after() {
System.out.println("4. @After");
}
}输出:
1. @Around 前置
2. @Before
=== 目标方法执行 ===
3. @AfterReturning
4. @After
5. @Around 后置4.2 异常情况
当目标方法抛出异常时,执行顺序变化如下:
@Around 前置代码
↓
@Before
↓
目标方法抛出异常
↓
@AfterThrowing
↓
@After
↓
@Around 捕获异常(若 Around 未捕获则继续抛出)示例:
@Aspect
public class ExceptionAspect {
@Around("execution(* com.example..doSomething(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("1. @Around 前置");
try {
return pjp.proceed();
} catch (Exception e) {
System.out.println("5. @Around 捕获异常: " + e.getMessage());
throw e;
}
}
@Before("execution(* com.example..doSomething(..))")
public void before() {
System.out.println("2. @Before");
}
@AfterThrowing("execution(* com.example..doSomething(..))")
public void afterThrowing(Exception e) {
System.out.println("3. @AfterThrowing: " + e.getMessage());
}
@After("execution(* com.example..doSomething(..))")
public void after() {
System.out.println("4. @After");
}
}输出(目标方法抛出异常时):
1. @Around 前置
2. @Before
=== 目标方法抛出异常 ===
3. @AfterThrowing
4. @After
5. @Around 捕获异常: ...重要的异常传播规则:
- 如果
@Around捕获了异常且不重新抛出,@AfterThrowing不会被触发,@AfterReturning也不会被触发(因为没有返回值)。 - 如果在
@After通知中抛出异常,该异常会覆盖原始异常。 - 如果
@Around不调用proceed(),目标方法和所有其他通知都不会执行。
4.3 同一切面内多个通知的执行顺序
在同一个切面类中,多个相同类型的通知(如两个 @Before)的执行顺序是不可预测的——Spring 不保证执行顺序。因此,不要在同一切面中声明多个相同类型的通知。
// 不推荐:两个 @Before 执行顺序不确定
@Aspect
public class BadAspect {
@Before("execution(* *(..))")
public void before1() {}
@Before("execution(* *(..))")
public void before2() {}
}解决方案:将不同优先级的通知拆分到不同切面,并通过 @Order 控制切面顺序。
五、多切面排序(@Order / @Ordered)
当多个切面作用于同一个连接点时,需要通过排序来控制通知的执行顺序。
5.1 使用 @Order 注解
@Component
@Aspect
@Order(1) // 数值越小优先级越高
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("LogAspect - Before");
}
}
@Component
@Aspect
@Order(2)
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("SecurityAspect - Before");
}
}执行结果:
LogAspect - Before // @Order(1) 优先级高,先执行
SecurityAspect - Before // @Order(2) 优先级低,后执行5.2 实现 Ordered 接口
@Order 注解方式的等效替代。
@Component
@Aspect
public class TransactionAspect implements Ordered {
@Override
public int getOrder() {
return 3;
}
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
// ...
return pjp.proceed();
}
}5.3 多切面嵌套执行顺序
当多个切面嵌套时,前置通知按 @Order 升序执行,后置通知按 @Order 降序执行(类似洋葱模型)。
@Component
@Aspect
@Order(1)
public class FirstAspect {
@Around("execution(* com.example..doSomething(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("First @Around 前置");
Object result = pjp.proceed();
System.out.println("First @Around 后置");
return result;
}
}
@Component
@Aspect
@Order(2)
public class SecondAspect {
@Around("execution(* com.example..doSomething(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Second @Around 前置");
Object result = pjp.proceed();
System.out.println("Second @Around 后置");
return result;
}
}输出:
First @Around 前置 // Order(1) 优先进入
Second @Around 前置 // Order(2) 后进入
=== 目标方法执行 ===
Second @Around 后置 // Order(2) 先退出
First @Around 后置 // Order(1) 后退出排列规律的形成原因是 Spring AOP 的责任链模式:优先级高的切面在最外层包裹优先级低的切面。
六、JoinPoint / ProceedingJoinPoint API
6.1 JoinPoint 接口
JoinPoint 是所有非环绕通知的第一个参数,提供连接点的上下文信息。
public interface JoinPoint {
/** 返回目标方法的参数 */
Object[] getArgs();
/** 返回被代理的目标对象 */
Object getTarget();
/** 返回代理对象本身 */
Object getThis();
/** 返回方法签名 — MethodSignature 可获取方法名、返回类型、参数类型等 */
Signature getSignature();
/** 返回连接点的静态信息(如声明类型、源码位置等) */
StaticPart getStaticPart();
/** 返回连接点类型标识字符串(如 "method-execution") */
String getKind();
/** 返回目标对象上的 toString 字符串 */
@Override
String toString();
}MethodSignature 详解:
@Before("execution(* com.example..*(..))")
public void before(JoinPoint jp) {
MethodSignature signature = (MethodSignature) jp.getSignature();
String methodName = signature.getName(); // 方法名
Class<?> returnType = signature.getReturnType(); // 返回类型
Class<?>[] parameterTypes = signature.getParameterTypes(); // 参数类型数组
Method method = signature.getMethod(); // 反射 Method 对象
Class<?> declaringType = signature.getDeclaringType(); // 声明类
}6.2 ProceedingJoinPoint 接口
ProceedingJoinPoint 是 @Around 通知专用的参数,继承了 JoinPoint 并增加了执行目标方法的能力。
public interface ProceedingJoinPoint extends JoinPoint {
/** 执行目标方法,使用原始参数 */
Object proceed() throws Throwable;
/** 执行目标方法,使用自定义参数覆盖原始参数 */
Object proceed(Object[] args) throws Throwable;
/** 执行目标方法(早期 API,功能与 proceed() 相同) */
Object proceed(Object arg) throws Throwable;
}6.3 完整 API 使用示例
@Around("execution(* com.example.service.*.*(..))")
public Object fullApiDemo(ProceedingJoinPoint pjp) throws Throwable {
// 1. 获取参数
Object[] args = pjp.getArgs();
System.out.println("参数个数: " + args.length);
// 2. 获取目标对象
Object target = pjp.getTarget();
System.out.println("目标对象: " + target.getClass().getSimpleName());
// 3. 获取代理对象
Object proxy = pjp.getThis();
System.out.println("代理对象: " + proxy.getClass().getSimpleName());
// 4. 获取方法签名
MethodSignature signature = (MethodSignature) pjp.getSignature();
System.out.println("方法名: " + signature.getName());
System.out.println("返回类型: " + signature.getReturnType());
System.out.println("参数类型: " + Arrays.toString(signature.getParameterTypes()));
// 5. 通过反射获取方法上的注解
Method method = signature.getMethod();
Log logAnnotation = method.getAnnotation(Log.class);
if (logAnnotation != null) {
System.out.println("Log 注解值: " + logAnnotation.value());
}
// 6. 执行目标方法
long start = System.nanoTime();
Object result = pjp.proceed();
long elapsed = System.nanoTime() - start;
System.out.println("执行耗时: " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms");
return result;
}七、源码分析:ReflectiveAspectJAdvisorFactory 切面解析流程
ReflectiveAspectJAdvisorFactory 是 Spring 解析 @AspectJ 注解切面的核心工厂类。它负责从标注了 @Aspect 的类中提取所有通知方法并将其封装为 Advisor 对象。
7.1 核心流程概览
@Aspect 类
↓
getAdvisors(MetadataAwareAspectInstanceFactory)
↓
getAdvisorMethods(aspectClass) ← 筛选 @Aspect 类中所有 @Before/@After/@Around 等通知
↓ 对每个通知方法
getAdvisor(method, ...)
↓
new InstantiationModelAwarePointcutAdvisorImpl(..., aspectInstanceFactory)
↓ 内部完成:
├── 解析 @Pointcut 表达式 → new AspectJExpressionPointcut
├── 确定通知类型 → getAdvice(...) 方法
└── 创建具体通知对象 → new AspectJMethodBeforeAdvice / AspectJAfterAdvice / AspectJMethodAroundAdvice ...7.2 getAdvisors 方法 —— 入口
// 源码位置:org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
String aspectName = maaif.getAspectMetadata().getAspectName();
validate(aspectClass);
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(maaif);
List<Advisor> advisors = new ArrayList<>();
// 遍历切面类中所有非 @Pointcut 的方法
for (Method method : getAdvisorMethods(aspectClass)) {
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
// 处理 @DeclareParents 引入增强
for (Method method : getDeclareParentsMethods(aspectClass)) {
Advisor advisor = getDeclareParentsAdvisor(method);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}7.3 getAdvisorMethods —— 筛选通知方法
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
List<Method> methods = new ArrayList<>();
// 遍历所有声明的方法(包括继承的)
for (Method method : aspectClass.getDeclaredMethods()) {
// 排除标注了 @Pointcut 的方法
if (method.getAnnotation(Pointcut.class) == null) {
methods.add(method);
}
}
// 按 @AspectJ 优先级规则的顺序排序
methods.sort(METHOD_COMPARATOR);
return methods;
}其中 METHOD_COMPARATOR 的定义确保 @Around > @Before > @After > @AfterReturning > @AfterThrowing 的解析顺序。
7.4 getAdvice —— 创建通知对象
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {
Class<?> aspectClass = aif.getAspectMetadata().getAspectClass();
// 关键:通过注解类型判断通知种类并创建对应的 Advice 对象
AbstractAspectJAdvice springAdvice;
if (aspectClass.getAnnotation(Aspect.class) == null) {
throw new AopConfigException("...");
}
if (candidateAdviceMethod.getAnnotation(Before.class) != null) {
// 创建前置通知
springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aif);
} else if (candidateAdviceMethod.getAnnotation(After.class) != null) {
// 创建最终通知
springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aif);
} else if (candidateAdviceMethod.getAnnotation(AfterReturning.class) != null) {
// 创建返回通知
springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aif);
} else if (candidateAdviceMethod.getAnnotation(AfterThrowing.class) != null) {
// 创建异常通知
springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aif);
} else if (candidateAdviceMethod.getAnnotation(Around.class) != null) {
// 创建环绕通知
springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aif);
} else {
return null;
}
// 设置切面名、声明顺序等信息
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrderInAspect);
return springAdvice;
}7.5 五种通知的内部实现类
| 通知类型 | Advice 实现类 | 核心方法 |
|---|---|---|
@Before | AspectJMethodBeforeAdvice | invoke(MethodInvocation) — 在 mi.proceed() 之前执行通知方法 |
@After | AspectJAfterAdvice | invoke(MethodInvocation) — 在 finally 块中执行通知方法 |
@AfterReturning | AspectJAfterReturningAdvice | afterReturning(Object, Method, Object[], Object) |
@AfterThrowing | AspectJAfterThrowingAdvice | afterThrowing(Throwable, Method, Object[], Object) |
@Around | AspectJAroundAdvice | invoke(MethodInvocation) — 直接调用环绕通知方法,由方法内部决定是否调用 proceed() |
AspectJAfterAdvice 源码 —— 典型的 finally 保障模式:
// 源码位置:org.springframework.aop.aspectj.AspectJAfterAdvice
public class AspectJAfterAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice {
public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod,
AspectJExpressionPointcut pointcut,
AspectInstanceFactory aif) {
super(aspectJBeforeAdviceMethod, pointcut, aif);
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
} finally {
// @After 在 finally 中执行 — 这就是为什么 @After 类似 try-finally
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
}AspectJAfterThrowingAdvice 源码:
// 源码位置:org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice {
public AspectJAfterThrowingAdvice(Method aspectJBeforeAdviceMethod,
AspectJExpressionPointcut pointcut,
AspectInstanceFactory aif) {
super(aspectJBeforeAdviceMethod, pointcut, aif);
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
} catch (Throwable ex) {
// 异常时才触发
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
}八、源码分析:AspectJPointcutAdvisor 切面排序
8.1 InstantiationModelAwarePointcutAdvisorImpl
当 ReflectiveAspectJAdvisorFactory 解析出每个通知方法后,会将它们包装为 InstantiationModelAwarePointcutAdvisorImpl 对象。这是 Advisor 接口的核心实现,承担了切面排序的关键职责。
// 源码位置:org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
class InstantiationModelAwarePointcutAdvisorImpl
implements InstantiationModelAwarePointcutAdvisor {
private final AspectJExpressionPointcut declaredPointcut;
private final Class<?> declaringClass;
private final String methodName;
private final AspectJAdviceFactory aspectJAdviceFactory;
private final MetadataAwareAspectInstanceFactory aspectInstanceFactory;
private final int declarationOrder; // 在切面内的声明顺序
private final String aspectName;
private volatile Pointcut pointcut;
private volatile Advice instantiatedAdvice; // 延迟创建的通知实例
private volatile boolean isInstantiated;
}8.2 排序实现机制
排序逻辑由 AspectJAwareAdvisorAutoProxyCreator 中的 sortAdvisors 方法完成:
// 源码位置:org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
@Override
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors =
new ArrayList<>(advisors.size());
for (Advisor element : advisors) {
partiallyComparableAdvisors.add(
new PartiallyComparableAdvisorHolder(element, declardecorationorder));
}
// 使用偏序排序(拓扑排序)
List<PartiallyComparableAdvisorHolder> sorted =
PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder holder : sorted) {
result.add(holder.getAdvisor());
}
return result;
} else {
return super.sortAdvisors(advisors);
}
}8.3 排序优先级规则
排序时遵循以下优先级(从左到右递减):
@Order(1) > @Order(2) > ... > 无 @Order 注解 > 默认最低具体实现通过 AnnotationAwareAspectJAutoProxyCreator 获取 @Order 值:
// 源码位置:org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator
// 排序时通过 Ordered 接口或 @Order 注解获取切面优先级
public class AspectJAwareAdvisorAutoProxyCreator {
/**
* 比较两个 Advisor 的排序优先级
* 数值越小优先级越高(越先执行前置通知)
*/
public static int compareAdvisors(Advisor a, Advisor b) {
// 获取 @Order 值或 Ordered.getOrder()
Integer orderA = getOrder(a);
Integer orderB = getOrder(b);
if (orderA == null && orderB == null) {
return Integer.compare(a.getOrder(), b.getOrder());
}
return Integer.compare(
orderA != null ? orderA : Integer.MAX_VALUE,
orderB != null ? orderB : Integer.MAX_VALUE
);
}
}8.4 AspectJ 排序偏序规则
Spring AOP 使用了偏序排序(Partial Order)来处理 Advisor 之间的排序关系,规则如下:
- 同一 Aspect 内的多个 Advisor:按
declarationOrder排序,即@Around>@Before>@After>@AfterReturning>@AfterThrowing。 - 不同 Aspect 间的 Advisor:根据
@Order注解值或Ordered.getOrder()返回值决定。 - 相同优先级的 Advisor:保持原添加顺序,但此行为不应依赖。
九、实战案例:金融交易系统 @LogAspect
本案例实现一个完整的金融交易日志切面,记录每笔交易的入参、出参和执行耗时,涵盖正文所述的全部知识点。
9.1 自定义日志注解
package com.example.finance.annotation;
import java.lang.annotation.*;
/**
* 交易日志注解 — 标注需要记录日志的交易方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TradeLog {
/** 交易类型描述 */
String value() default "";
/** 是否打印参数 */
boolean printParams() default true;
/** 是否打印返回值 */
boolean printResult() default true;
}9.2 交易服务接口与实现
package com.example.finance.service;
public interface TradeService {
/**
* 转账交易
* @param fromAccount 转出账户
* @param toAccount 转入账户
* @param amount 转账金额
* @return 交易流水号
*/
String transfer(String fromAccount, String toAccount, java.math.BigDecimal amount);
/**
* 余额查询
* @param account 账户号
* @return 余额
*/
java.math.BigDecimal queryBalance(String account);
}package com.example.finance.service.impl;
import com.example.finance.annotation.TradeLog;
import com.example.finance.service.TradeService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class TradeServiceImpl implements TradeService {
@TradeLog("转账交易")
@Override
public String transfer(String fromAccount, String toAccount, BigDecimal amount) {
// 模拟业务处理
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("转账金额必须大于 0");
}
// 模拟耗时
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 返回交易流水号
return "TXN" + UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase();
}
@TradeLog(value = "余额查询", printResult = true)
@Override
public BigDecimal queryBalance(String account) {
// 模拟耗时
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new BigDecimal("10000.00");
}
}9.3 完整 @LogAspect 切面实现
package com.example.finance.aspect;
import com.example.finance.annotation.TradeLog;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 交易日志切面 — 记录每笔交易的入参、出参和耗时
*
* 排序为 1,优先级最高(在其他切面之前执行)
*/
@Component
@Aspect
@Order(1)
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/**
* Jackson ObjectMapper — 用于序列化参数和返回值为 JSON
* 实际项目中应从 Spring 容器注入
*/
private final ObjectMapper objectMapper = new ObjectMapper();
/** 交易计数器(统计用途) */
private final AtomicLong tradeCounter = new AtomicLong(0);
/** 交易耗时统计(Map<方法名, 总耗时>) */
private final Map<String, Long> elapsedStats = new ConcurrentHashMap<>();
// ===================== Pointcut 定义 =====================
/**
* 切入点:所有标注了 @TradeLog 的方法
*/
@Pointcut("@annotation(com.example.finance.annotation.TradeLog)")
public void tradeLogPointcut() {}
/**
* 切入点:com.example.finance.service 包下所有方法
*/
@Pointcut("within(com.example.finance.service..*)")
public void serviceLayer() {}
/**
* 组合切入点:service 层中被 @TradeLog 标注的方法
*/
@Pointcut("tradeLogPointcut() && serviceLayer()")
public combinedPointcut() {}
// ===================== 通知定义 =====================
/**
* 环绕通知 — 核心日志逻辑:记录入参、执行耗时、出参
*/
@Around("combinedPointcut()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
// 解析 @TradeLog 注解
TradeLog tradeLog = getTradeLogAnnotation(pjp);
if (tradeLog == null) {
// 保险:没有注解时直接执行
return pjp.proceed();
}
String methodName = pjp.getSignature().toShortString();
long tradeId = tradeCounter.incrementAndGet();
// ===== 阶段 1:记录入参 =====
log.info("┌─────────────────────────────────────────────");
log.info("│ [交易 #{}, {}] {}", tradeId, tradeLog.value(), methodName);
if (tradeLog.printParams()) {
Object[] args = pjp.getArgs();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String[] paramNames = signature.getParameterNames();
try {
// 使用 Jackson 美化打印参数
for (int i = 0; i < args.length; i++) {
String paramInfo = (paramNames != null ? paramNames[i] : "arg" + i)
+ " = " + maskSensitiveData(args[i], paramNames, i);
log.info("│ 入参[{}]: {}", i, paramInfo);
}
} catch (Exception e) {
log.warn("│ 入参序列化失败: {}, args={}", e.getMessage(), Arrays.toString(args));
}
}
// ===== 阶段 2:执行目标方法并计时 =====
long startTime = System.nanoTime();
Object result;
try {
result = pjp.proceed();
} catch (Throwable t) {
// ===== 异常分支:记录异常信息 =====
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
log.error("│ [交易 #{}] 异常: {} ({}ms)", tradeId, t.getClass().getSimpleName(),
t.getMessage(), elapsed);
log.info("└─────────────────────────────────────────────");
// 更新统计
updateStats(methodName, elapsed);
throw t;
}
// ===== 阶段 3:记录出参和耗时 =====
long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
if (tradeLog.printResult()) {
try {
String resultJson = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(result);
log.info("│ 出参: {}", resultJson.length() > 500
? resultJson.substring(0, 500) + "..." : resultJson);
} catch (Exception e) {
log.warn("│ 出参序列化失败: {}", String.valueOf(result));
}
}
log.info("│ 耗时: {}ms", elapsed);
log.info("└─────────────────────────────────────────────");
// 更新统计
updateStats(methodName, elapsed);
return result;
}
/**
* 获取 @TradeLog 注解
*/
private TradeLog getTradeLogAnnotation(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
return signature.getMethod().getAnnotation(TradeLog.class);
}
// ===================== 工具方法 =====================
/**
* 脱敏处理 — 对敏感字段(如账户号)进行脱敏
*/
private String maskSensitiveData(Object arg, String[] paramNames, int index) {
// 简单脱敏:对于账户类参数只显示后4位
if (arg instanceof String && paramNames != null
&& paramNames[index].toLowerCase().contains("account")) {
String account = (String) arg;
if (account.length() > 4) {
return "****" + account.substring(account.length() - 4);
}
}
try {
return objectMapper.writeValueAsString(arg);
} catch (Exception e) {
return String.valueOf(arg);
}
}
/**
* 更新耗时统计
*/
private void updateStats(String methodName, long elapsed) {
elapsedStats.merge(methodName, elapsed, Long::sum);
}
/**
* 获取统计信息(可暴露为 Endpoint 供监控使用)
*/
public Map<String, Long> getElapsedStats() {
return new ConcurrentHashMap<>(elapsedStats);
}
public long getTotalTradeCount() {
return tradeCounter.get();
}
}9.4 其他通知的补充用法
为了完整展示五种通知类型,在日志切面中也可以组合使用其他通知(实际项目中通常只需 @Around):
@Component
@Aspect
@Order(2)
public class MonitoringAspect {
/** 成功率计数器 */
private final AtomicLong successCount = new AtomicLong(0);
private final AtomicLong failureCount = new AtomicLong(0);
@Pointcut("@annotation(com.example.finance.annotation.TradeLog)")
public void tradeLogPointcut() {}
@Before("tradeLogPointcut()")
public void preMonitor(JoinPoint jp) {
// 在目标方法执行前记录开始时间到 ThreadLocal
MonitorContext.startTimer(jp.getSignature().toShortString());
}
@AfterReturning("tradeLogPointcut()")
public void onSuccess() {
successCount.incrementAndGet();
}
@AfterThrowing("tradeLogPointcut()")
public void onFailure() {
failureCount.incrementAndGet();
}
@After("tradeLogPointcut()")
public void postMonitor(JoinPoint jp) {
// 在 finally 中记录方法执行耗时
MonitorContext.stopTimer(jp.getSignature().toShortString());
}
}/**
* 监控上下文 — 用于跨通知传递数据
* 在 @Before 中设置,在 @After 中读取
*/
public class MonitorContext {
private static final ThreadLocal<Map<String, Long>> timerHolder =
ThreadLocal.withInitial(HashMap::new);
public static void startTimer(String methodName) {
timerHolder.get().put(methodName, System.nanoTime());
}
public static long stopTimer(String methodName) {
Long start = timerHolder.get().remove(methodName);
if (start == null) return 0;
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
}
public static void clear() {
timerHolder.remove();
}
}9.5 运行示例与输出
测试代码:
@Component
public class TradeServiceRunner implements CommandLineRunner {
@Autowired
private TradeService tradeService;
@Override
public void run(String... args) {
// 正常交易
String txnId = tradeService.transfer("6222021234567890",
"6222020987654321", new BigDecimal("500.00"));
System.out.println("交易完成,流水号: " + txnId);
// 查询余额
BigDecimal balance = tradeService.queryBalance("6222021234567890");
System.out.println("余额: " + balance);
}
}控制台输出:
┌─────────────────────────────────────────────
│ [交易 #1, 转账交易] TradeServiceImpl.transfer(..)
│ 入参[0]: arg0 = ****7890
│ 入参[1]: arg1 = ****4321
│ 入参[2]: arg2 = 500.00
│ 出参: "TXN1A2B3C4D5E6F7G8"
│ 耗时: 52ms
└─────────────────────────────────────────────
交易完成,流水号: TXN1A2B3C4D5E6F7G8
┌─────────────────────────────────────────────
│ [交易 #2, 余额查询] TradeServiceImpl.queryBalance(..)
│ 入参[0]: arg0 = ****7890
│ 出参: 10000.00
│ 耗时: 21ms
└─────────────────────────────────────────────
余额: 10000.00