Spring AOP实践:@AspectJ注解全解析

以下内容是 Spring Framework 官方文档中关于 "@AspectJ support" 的一节内容(5.4 节) ,它详细介绍了如何在 Spring 中使用 @AspectJ 风格来定义和使用切面。

我们已经理解了 AOP 的基本概念(来自你之前提供的文本),现在这个文件 54 Aspe.txt 是更进一步的实践指南。它告诉你:"既然我们知道 AOP 是什么了,那具体怎么用注解的方式写一个切面?"


下面我将结合你之前提供的背景知识,对 54 Aspe.txt 文件的内容进行系统性解读与翻译 ,帮助你真正掌握如何在 Spring 项目中使用 @AspectJ


📚 文件核心主题:@AspectJ Support in Spring

💡 简单说:这一节讲的是 如何用 Java 注解的方式(模仿 AspectJ 语法)来写 Spring AOP 切面

虽然名字叫 @AspectJ,但它不依赖 AspectJ 编译器或织入器,而是:

  • 使用 AspectJ 提供的注解和表达式解析库
  • Spring AOP 框架在运行时通过代理实现增强

所以它是 "注解风格像 AspectJ,但底层还是 Spring AOP"。


🔧 第一步:启用 @AspectJ 支持

要让 Spring 能识别 @Aspect 注解的类,必须先开启支持。

✅ Java 配置方式

java 复制代码
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

✅ XML 配置方式

xml 复制代码
<aop:aspectj-autoproxy/>

📌 作用 :开启后,Spring 会自动扫描所有带有 @Aspect 注解的 Bean,并根据其定义的 pointcut 和 advice 来创建代理对象。

🔧 前提条件

  • 必须引入 aspectjweaver.jar 依赖(Maven/Gradle 中添加即可)

🧩 第二步:声明一个切面(@Aspect)

java 复制代码
@Aspect
@Component // 记住!要让 Spring 扫描到,必须加上 @Component 或其他组件注解
public class MyAspect {

    // 这里写 pointcut、advice...
}

⚠️ 注意:

  • @Aspect 只是标记这是一个切面类。
  • 但它本身不会被 Spring 管理,除非加上 @Component 或手动注册为 Bean。
  • 切面自己不能被其他切面增强(即不能对切面应用 advice)。

🔍 第三步:定义切入点(Pointcut)

切入点决定了"哪些方法需要被拦截"。

基本语法:

java 复制代码
@Pointcut("execution(* transfer(..))")
private void anyOldTransfer() {}
  • @Pointcut:声明一个命名的切入点。
  • "execution(* transfer(..))":这是 AspectJ 表达式 ,匹配名为 transfer 的方法。
  • anyOldTransfer():是切入点的名字,后面可以引用它。

🔁 组合多个 Pointcut

java 复制代码
@Pointcut("execution(public * *(..))")
public void anyPublicOperation() {}

@Pointcut("within(com.xyz.myapp.trading..*)")
public void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
public void tradingOperation() {}
  • &&:且
  • ||:或
  • !:非

推荐把常用 pointcut 抽成公共类,比如:

java 复制代码
@Aspect
public class CommonPointcuts {
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}

    @Pointcut("execution(* com.xyz.myapp.service.*.*(..))")
    public void businessService() {}
}

这样别的切面可以直接引用:@Before("CommonPointcuts.businessService()")


⚠️ 常见 Pointcut 设计器(Designators)

设计器 含义 示例
execution() 匹配方法执行 execution(* com.service.UserService.save*(..))
within() 匹配某个包下的所有方法 within(com.service..*)
this() 代理对象类型匹配 this(com.service.UserService)
target() 目标对象类型匹配 target(com.service.UserService)
args() 方法参数类型匹配 args(java.lang.String)
@annotation() 方法上有指定注解 @annotation(Loggable)
bean() 匹配 Spring 容器中的 bean 名称 bean(*Service)

✅ 最常用的是:execution, within, @annotation, bean


🛠 第四步:声明通知(Advice)

通知就是"在目标方法前后做什么"。

1. 前置通知 @Before

java 复制代码
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
    System.out.println("检查数据库访问权限...");
}

2. 后置返回通知 @AfterReturning

java 复制代码
@AfterReturning(
    pointcut = "businessService()",
    returning = "result"
)
public void logResult(Object result) {
    System.out.println("方法返回值:" + result);
}

3. 异常通知 @AfterThrowing

java 复制代码
@AfterThrowing(
    pointcut = "dataAccessOperation()",
    throwing = "ex"
)
public void handleException(DataAccessException ex) {
    System.out.println("捕获异常:" + ex.getMessage());
}

4. 最终通知 @After

java 复制代码
@After("businessService()")
public void releaseResource() {
    System.out.println("释放资源(类似 finally)");
}

5. 环绕通知 @Around

最强大,可控制是否执行原方法:

java 复制代码
@Around("businessService()")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();

    try {
        Object result = pjp.proceed(); // 执行原方法
        return result;
    } finally {
        long elapsed = System.currentTimeMillis() - start;
        System.out.println(pjp.getSignature() + " 耗时:" + elapsed + "ms");
    }
}

🔄 Advice 执行顺序

当多个切面作用于同一个方法时,执行顺序如下:

顺序 类型
1 @Around(进入)
2 @Before
3 原方法执行
4 @After(最终)
5 @AfterReturning / @AfterThrowing
6 @Around(退出)

🎯 控制优先级:

  • 使用 @Order 注解或实现 Ordered 接口
  • 数值越小,优先级越高
java 复制代码
@Aspect
@Order(1)
public class HighPriorityAspect { ... }

🧪 实战案例:重试机制切面

文档中给了一个非常实用的例子:当数据库操作因死锁失败时,自动重试几次

java 复制代码
@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private int maxRetries = 2;

    @Around("@annotation(Idempotent)") // 只对幂等方法重试
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int attempts = 0;
        do {
            try {
                return pjp.proceed(); // 尝试执行
            } catch (PessimisticLockingFailureException e) {
                attempts++;
                if (attempts >= maxRetries) {
                    throw e;
                }
            }
        } while (attempts <= maxRetries);
        return null;
    }

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    @Override
    public int getOrder() {
        return 1; // 高优先级
    }
}

配合自定义注解:

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // 标记幂等方法
}

使用:

java 复制代码
@Service
public class OrderService {

    @Idempotent
    @Transactional
    public void createOrder(Order order) {
        // 可能发生死锁,但会被自动重试
    }
}

✅ 总结:如何理解这份文件?

层次 内容
🎯 目标 教你如何用 @Aspect 注解写切面
🔧 技术栈 Spring AOP + AspectJ 注解语法(无需编译器)
🧱 核心步骤 启用 → 写 Aspect 类 → 定义 Pointcut → 写 Advice
🛠 关键工具 @Pointcut, @Before, @After, @Around, bean(), @annotation()
📦 最佳实践 公共 pointcut 抽象、合理使用 @Order、避免 self-invocation

🚀 下一步建议

你可以动手尝试:

  1. 创建一个 LogAspect,用 @Around 打印所有 controller 方法的请求耗时。
  2. 写一个 SecurityAspect,用 @Before 拦截带 @RequiresAuth 注解的方法做权限校验。
  3. 使用 bean(*Service) 匹配所有 Service 层方法,统一加日志。

如果你希望我帮你写一个完整的示例项目结构或 Maven 依赖配置,请告诉我你的需求 😊