AspectJ 是 Java 生态中最强大、最成熟的 AOP 框架,Spring AOP 的底层就依赖 AspectJ 的注解和切点表达式语法。
Spring AOP vs AspectJ
| 对比维度 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 动态代理(JDK/CGLIB) | 字节码织入(编译期/类加载期/运行期) |
| 织入时机 | 运行时 | 编译期、类加载期、运行期 |
| 性能 | 一般(代理调用开销) | 更好(直接执行增强代码) |
| 支持粒度 | 仅方法级别的连接点 | 字段、构造器、静态方法、异常处理等 |
| 目标对象限制 | 需要 Spring 容器管理 | 任意 Java 对象 |
| private/protected 方法 | ❌ 不支持 | ✅ 支持(通过 Load-Time Weaving) |
| IDE 支持 | 一般 | Eclipse/AJDT 插件支持 |
AspectJ 五种织入时机
| 织入类型 | 时机 | 说明 |
|---|---|---|
| 编译期织入 (CTW) | 源代码编译时 | 需要 AspectJ 编译器(ajc) |
| 编译后织入 (BTW) | 编译后处理 class 文件 | 修改已有 class |
| 加载期织入 (LTW) | 类加载到 JVM 时 | 通过 Java Agent 实现,最灵活 |
| 运行时织入 | 运行时 | Spring AOP 采用的方式(非原生 AspectJ) |
| 混合织入 | 组合使用 | 部分代码编译期织入 + LTW |
核心语法
1. 切点表达式完整语法
execution([权限修饰符] 返回值类型 [类全限定名.]方法名(参数列表) [throws 异常类型])
示例:
bash
// 任意 public 方法
execution(public * *(..))
// 指定包下所有方法
execution(* com.example.service..*.*(..)) // .. 表示子包
// 参数匹配
execution(* *.*(String, int)) // 两个特定参数
execution(* *.*(..)) // 任意参数
execution(* *.*(String, ..)) // 第一个是 String,后面任意
// 返回值匹配
execution(* com.example.User+.*(..)) // User 及其子类的方法
2. 常用切点指示器
| 指示器 | 说明 | 示例 |
|---|---|---|
execution |
匹配方法执行 | execution(* com..*.*(..)) |
within |
匹配类型内所有方法 | within(com.example.service.*) |
this |
匹配代理对象类型 | this(com.example.UserService) |
target |
匹配目标对象类型 | target(com.example.UserService) |
args |
匹配参数类型 | args(String, int) |
@within |
匹配有某注解的类型 | @within(org.springframework.stereotype.Service) |
@target |
匹配目标对象有注解 | @target(com.example.annotation.MyAnnotation) |
@args |
匹配参数有注解 | @args(com.example.annotation.Valid) |
@annotation |
匹配方法有注解 | @annotation(com.example.annotation.Log) |
bean |
匹配 Spring Bean 名称 | bean(userService) |
LTW(加载期织入)配置示例
1. 创建切面
java
@Aspect
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
Object result = pjp.proceed();
long end = System.nanoTime();
System.out.println(pjp.getSignature() + " 耗时: " + (end - start) + "ns");
return result;
}
// 拦截字段访问
@Before("get(String com.example.User.name)")
public void beforeGetName() {
System.out.println("即将读取 name 字段");
}
}
2. META-INF/aop.xml
XML
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-verbose -showWeaveInfo">
<!-- 指定需要织入的包 -->
<include within="com.example.service..*"/>
<include within="com.example.entity..*"/>
</weaver>
<aspects>
<aspect name="com.example.aspect.PerformanceAspect"/>
</aspects>
</aspectj>
3. JVM 启动参数
bash
-javaagent:path/to/aspectjweaver.jar
Spring 集成 AspectJ
方式一:使用注解(最常用)
java
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 启用 AspectJ 注解支持
@ComponentScan("com.example")
public class AppConfig {
}
方式二:XML 配置
XML
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="logAspect" class="com.example.aspect.LogAspect"/>
高级特性示例
1. 拦截字段赋值
java
@Aspect
public class FieldMonitorAspect {
@Before("set(* com.example.User.age)")
public void beforeSetAge(JoinPoint jp) {
int newAge = (int) jp.getArgs()[0];
if (newAge < 0 || newAge > 150) {
throw new IllegalArgumentException("年龄非法: " + newAge);
}
}
}
2. 拦截异常处理
java
@Aspect
public class ExceptionHandlerAspect {
@AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "ex")
public void handleException(Exception ex) {
System.out.println("捕获异常: " + ex.getMessage());
// 自定义异常处理逻辑
}
// 拦截 Handler 方法本身
@Before("handler(java.lang.Exception+)")
public void beforeExceptionHandler() {
System.out.println("即将处理异常");
}
}
3. 多重切点组合
java
@Aspect
public class ComplexPointcutAspect {
// 定义复用切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("@annotation(com.example.annotation.Auditable)")
public void auditableMethod() {}
@Pointcut("bean(*Service)")
public void serviceBean() {}
// 组合使用
@Before("serviceLayer() && auditableMethod() && !serviceBean()")
public void beforeCombined() {
System.out.println("满足多个条件的切点");
}
}
4. 软升级(Softening)异常
java
// 将受检异常转换为非受检异常
@DeclareSoft("execution(* *.*(..))")
public static SoftException softException;
性能优化建议
| 优化项 | 说明 |
|---|---|
| 窄化切点 | 切点表达式越精确,匹配速度越快 |
避免使用 * 通配符过多 |
减少模式匹配开销 |
使用 within 替代 execution |
within 匹配更快(基于类型) |
| 优先使用编译期织入 | 运行时开销最小 |
| 减少 AspectJ 切面数量 | 每个切面增加匹配检查 |