以下内容是 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 |
🚀 下一步建议
你可以动手尝试:
- 创建一个
LogAspect,用@Around打印所有 controller 方法的请求耗时。 - 写一个
SecurityAspect,用@Before拦截带@RequiresAuth注解的方法做权限校验。 - 使用
bean(*Service)匹配所有 Service 层方法,统一加日志。
如果你希望我帮你写一个完整的示例项目结构或 Maven 依赖配置,请告诉我你的需求 😊