两种AOP实现方式对比:Spring AOP vs AspectJ
面向切面编程(AOP)有 Spring AOP 和 AspectJ 两种实现方式,本文对这两种方式做个对比。关于AOP的介绍可以参考:https://blog.csdn.net/taotiezhengfeng/article/details/149515395
1. 核心区别概览
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 实现原理 | 运行时动态代理 | 编译时/类加载时字节码织入 |
| 代理方式 | JDK动态代理 + CGLIB | 直接修改字节码 |
| 连接点支持 | 仅方法执行 | 方法、构造器、字段、静态初始化等 |
| 性能 | 运行时创建代理,有一定开销 | 几乎无运行时开销 |
| 集成复杂度 | 简单(Spring原生支持) | 需要额外构建工具 |
| 适用范围 | Spring管理的Bean | 任何Java类 |
| 适用场景 | Spring应用中的方法级切面 | 需要细粒度控制的复杂AOP需求 |
2. Spring AOP 介绍
2.1 实现原理
Spring AOP 基于代理模式,在运行时动态创建代理对象。
- 当从 Spring 容器获取目标类的 Bean 时,实际拿到的是 代理对象。
- 调用代理对象的方法时,会先经过 AOP 拦截器链,再调用原始方法。
- spring AOP原理详细介绍参考:https://blog.csdn.net/taotiezhengfeng/article/details/155982763
2.1.1 JDK动态代理(接口代理)
JDK动态代理特点:
- ✅ 只能代理实现了接口的类
- ✅ 基于Java反射机制
- ✅ 代理对象和目标对象实现同一接口
目标类UserServiceImpl 实现了接口 UserService,Spring AOP 会默认使用 JDK 动态代理。
// 目标接口
public interface UserService {
User findById(Long id);
}
// 目标实现
@Service
public class UserServiceImpl implements UserService {
public User findById(Long id) {
return userRepository.findById(id);
}
}
// 切面定义
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.UserService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法调用前: " + joinPoint.getSignature().getName());
}
}
Spring AOP会基于接口UserService,自动创建JDK动态代理:
// Spring会创建JDK动态代理
UserService proxy = (UserService) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
// 执行通知逻辑
// 调用目标方法
return method.invoke(target, args);
}
}
);
2.1.2 CGLIB代理(类代理)
CGLIB代理特点:
- ✅ 可以代理没有接口的类
- ✅ 通过继承创建子类
- ❌ 不能代理final类和final方法
目标类OrderService没有实现接口,Spring 会使用 CGLIB 代理(通过继承目标类生成子类)。
// 目标接口: 没有接口的类
@Service
public class OrderService {
public void createOrder(Order order) {
// 业务逻辑
}
}
// 切面定义
@Aspect
@Component
public class OrderLoggingAspect {
@Around("execution(* com.example.service.OrderService.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " 执行时间: " + (endTime - startTime) + "ms");
return result;
}
}
Spring AOP使用CGLIB,自动生成OrderService的代理子类:
// Spring使用CGLIB生成子类
public class OrderService$$EnhancerBySpringCGLIB extends OrderService {
private MethodInterceptor methodInterceptor;
@Override
public void createOrder(Order order) {
// 执行通知逻辑
methodInterceptor.intercept(this, ...);
// 调用父类方法
super.createOrder(order);
}
}
2.2 处理机制
Spring AOP中:
@Aspect必须配合@Component才能被 Spring 容器管理- 切面类必须是 Spring Bean
- 依赖 Spring 的代理机制
2.3 限制
2.3.1 连接点限制
Spring AOP 只能拦截方法执行:
- ✅ 方法调用
- ❌ 字段访问
- ❌ 构造器调用
- ❌ 静态方法调用
- ❌ 对象初始化
2.3.2 只对外部调用生效
AOP代理只对外部调用生效,内部方法调用会绕过代理对象,不会生效:
@Service
public class UserService {
public void externalMethod() {
internalMethod(); // ❌ 这个内部调用不会被AOP拦截!
}
@Transactional
public void internalMethod() {
// 业务逻辑
}
}
3. AspectJ 介绍
AspectJ 是一个功能更强大的独立 AOP 框架,提供了比 Spring AOP 更丰富的连接点类型和织入方式。
3.1 实现原理
AspectJ 直接修改字节码,将切面逻辑织入到目标类中。
3.1.1 编译时织入(Compile-time weaving)
AspectJ的第1种织入方式是编译时织入(Compile-time weaving)。对应的 pom.xml 配置如下:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
目标类和切面:
// 目标类 UserService
// 目标类
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter...
}
public class UserService {
public void processUser(User user) {
// 访问字段会触发 fieldAccess 切点
System.out.println("处理用户: " + user.name);
}
}
// 切面;AspectJ支持各种类型的连接点
@Aspect
public class ComprehensiveAspect {
// 方法执行
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod() {}
// 构造器调用
@Before("call(com.example.model.User.new(..))")
public void beforeConstructor() {}
// 字段访问
@Before("get(private String com.example.model.User.name)")
public void beforeFieldGet() {}
@Before("set(private String com.example.model.User.name)")
public void beforeFieldSet() {}
// 静态初始化
@Before("staticinitialization(com.example.service.UserService)")
public void beforeStaticInit() {}
// 异常处理
@Before("handler(java.lang.Exception)")
public void beforeExceptionHandler() {}
}
经过ajc编译器处理后,编译后的字节码中直接包含AOP逻辑:
// 经过ajc编译器处理后,字节码中直接包含AOP逻辑
public class UserService {
public void createUser(User user) {
// ← 切面逻辑直接插入到这里
// 业务逻辑
// ← 切面逻辑直接插入到这里
}
}
3.1.2 类加载时织入(Load-time weaving)
AspectJ的第2种织入方式是类加载时织入(Load-time weaving),需要配置META-INF/aop.xml 和启动参数。
配置 META-INF/aop.xml:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-verbose -showWeaveInfo">
<include within="com.example..*"/>
</weaver>
<aspects>
<aspect name="com.example.LoggingAspect"/>
</aspects>
</aspectj>
启动参数:
java -javaagent:/path/to/aspectjweaver.jar MyApp
3.2 处理机制
AspectJ:
@Aspect本身就足够,不需要@Component- 切面类不需要是 Spring Bean(但可以是)
- 通过字节码织入,不依赖 Spring 容器
4. Spring AOP 与 AspectJ 对比分析
4.1 性能对比
Spring AOP:
- 每次方法调用都有代理开销
- 反射调用有一定的性能损耗
- 适合大多数业务场景
AspectJ:
- 编译时织入:零运行时开销
- 类加载时织入:一次性织入开销
- 适合高性能要求场景
4.2 使用复杂度
Spring AOP:几乎零配置,Spring Boot自动启用
// 几乎零配置,Spring Boot自动启用
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* service.*(..))")
public void log(JoinPoint jp) {}
}
AspectJ:需要额外的构建配置或JVM参数
// 需要额外的构建配置或JVM参数
@Aspect
public class LoggingAspect {
@Before("execution(* service.*(..))")
public void log(JoinPoint jp) {}
}
// 还需要配置 ajc 编译器或 LTW
4.3 功能范围对比
| 场景 | Spring AOP | AspectJ |
|---|---|---|
| Service层方法拦截 | ✅ 完美支持 | ✅ 支持 |
| 私有方法拦截 | ❌ 不支持 | ✅ 支持 |
| 构造器拦截 | ❌ 不支持 | ✅ 支持 |
| 字段访问监控 | ❌ 不支持 | ✅ 支持 |
| 内部方法调用拦截 | ❌ 不支持 | ✅ 支持 |
| 静态方法拦截 | ❌ 不支持 | ✅ supports |
4.4 方法层面拦截的对比
在方法层面的拦截上,AspectJ 和 Spring AOP 使用完全相同的注解语法:
// 这段代码在 Spring AOP 和 AspectJ 中都能正常编译和运行
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void logBefore(JoinPoint jp) {}
@Around("@annotation(Cacheable)")
public Object handleCache(ProceedingJoinPoint pjp, Cacheable cacheable) {}
}
但是,有重要的区别需要注意!
4.4.1 注解处理机制不同
Spring AOP:
@Aspect必须配合@Component才能被 Spring 容器管理- 切面类必须是 Spring Bean
- 依赖 Spring 的代理机制
AspectJ:
@Aspect本身就足够,不需要@Component- 切面类不需要是 Spring Bean(但可以是)
- 通过字节码织入,不依赖 Spring 容器
// Spring AOP - 必须这样写
@Aspect
@Component // ← 必需
public class SpringLoggingAspect { }
// AspectJ - 可以这样写
@Aspect // ← 足够了
public class AspectJLoggingAspect { }
4.4.2 Pointcut 表达式范围不同
虽然方法层面的表达式相同,但 AspectJ 支持更多表达式类型:
// 这些在两种框架中都支持(方法层面)
execution(* service.*(..))
call(* service.*(..)) // 注意:Spring AOP 不支持 call()
within(com.example.service..*)
@annotation(Cacheable)
// 这些只有 AspectJ 支持
handler(Exception)
staticinitialization(com.example.service.*)
特别注意: call() vs execution()
- Spring AOP 只支持
execution() - AspectJ 同时支持
call()和execution()
// Spring AOP 中,这行会报错或无效
@Before("call(* com.example.service.*.*(..))") // ❌ 不支持
// Spring AOP 中,只能用 execution
@Before("execution(* com.example.service.*.*(..))") // ✅ 支持
4.4.3 JoinPoint 对象能力不同
@Before("execution(* service.*(..))")
public void advice(JoinPoint jp) {
// 在 Spring AOP 中,某些方法可能返回 null 或有限信息
System.out.println(jp.getSourceLocation()); // Spring AOP 可能返回 null
// 在 AspectJ 中,提供完整的源码位置信息
// SourceLocation 包含文件名、行号等详细信息
}
4.4.4 参数绑定的严格程度不同
Spring AOP:
- 参数绑定相对宽松
- 某些复杂的绑定场景可能不支持
AspectJ:
- 参数绑定更严格和完整
- 支持更复杂的绑定表达式
4.4.5 异常处理的差异
@AfterThrowing(
pointcut = "execution(* service.*(..))",
throwing = "ex"
)
public void handleException(JoinPoint jp, Exception ex) {
// Spring AOP: ex 只能捕获被代理方法抛出的异常
// AspectJ: ex 可以捕获任何匹配连接点抛出的异常
}
4.4.5 兼容性建议
如果你只使用方法层面的 AOP,可以这样写以保证兼容性:
// ✅ 安全的写法(在两种框架中都能工作)
@Aspect
@Component // 加上这个确保 Spring AOP 能识别
public class CompatibleAspect {
// 只使用 execution,不用 call
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceMethods() {}
// 使用标准的通知类型
@Before("serviceMethods()")
public void before(JoinPoint jp) {}
@Around("@annotation(Cacheable)")
public Object around(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
return pjp.proceed();
}
}
避免使用的特性(如果需要兼容):
// ❌ 避免使用(Spring AOP 不支持)
@Pointcut("call(* service.*(..))") // 用 execution 代替
@Pointcut("get(* User.name)") // 字段访问
@Pointcut("handler(IllegalArgumentException)") // 异常处理器
// ❌ 避免复杂的参数绑定
@Before("args(.., @Validated (*), ..)") // 复杂的注解绑定
4.5 实际应用场景
选择 Spring AOP 当:
- 开发Spring应用程序
- 只需要方法级别的AOP
- 希望简单集成,无需额外工具
- 处理事务、日志、安全等常见横切关注点
选择 AspectJ 当:
- 需要拦截构造器、字段等非方法连接点
- 需要监控私有方法或内部调用
- 对性能要求极高(编译时织入)
- 非Spring环境下的AOP需求
- 需要细粒度的程序行为监控
总结:
- Spring AOP 是"够用就好"的选择,简单、集成度高、适合大多数企业应用
- AspectJ 是"功能完整"的选择,强大、灵活、适合特殊需求或高性能场景
对于90%的Spring应用开发,Spring AOP完全足够。只有在遇到Spring AOP无法解决的特殊需求时,才需要考虑AspectJ。
5. Spring + AspectJ 的混合使用
Spring也支持使用AspectJ的注解语法,但仍然基于Spring的代理机制:
// 使用AspectJ注解,但由Spring AOP处理
@Aspect
@Component
public class HybridAspect {
// 这仍然是Spring AOP,只支持方法级别的连接点
@Before("execution(* service.*(..))")
public void advice() {}
}
如果要在Spring中使用真正的AspectJ功能,需要额外配置:
// 启用AspectJ的load-time weaving
@EnableLoadTimeWeaving
@SpringBootApplication
public class Application {
// 需要配置LTW和aspectjweaver依赖
}
6.如何判断使用的是 Spring AOP或AspectJ?
接手一个新项目,如何判断项目中使用的是 AspectJ 还是 Spring AOP 呢?
6.1. 通过依赖和配置判断
Spring AOP 的典型特征
- 只需要 Spring 相关的 AOP 依赖
- 不需要
aspectjrt、aspectjweaver等 AspectJ 专用依赖 - 无需特殊构建插件
<!-- Maven 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 或者 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
AspectJ 的典型特征:
- 包含
aspectjrt、aspectjweaver等依赖 - 可能有
aspectj-maven-plugin构建插件 - 启动时可能需要
-javaagent:aspectjweaver.jar参数
<!-- 编译时织入需要的依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.x</version>
</dependency>
<!-- 类加载时织入需要的依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.x</version>
</dependency>
<!-- 编译时织入需要的构建插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
6.2. 通过切面代码的限制判断
6.2.1 尝试使用非方法连接点
@Aspect
@Component
public class TestAspect {
// 如果这行代码能正常工作,说明是真正的 AspectJ
@Before("call(com.example.User.new(..))") // 构造器调用
public void beforeConstructor() {
System.out.println("构造器被调用");
}
// 如果这行代码能正常工作,说明是真正的 AspectJ
@Before("get(private String com.example.User.name)") // 字段读取
public void beforeFieldGet() {
System.out.println("读取字段");
}
// 这个在 Spring AOP 和 AspectJ 中都能工作
@Before("execution(* com.example.service.*.*(..))") // 方法执行
public void beforeMethod() {
System.out.println("方法执行");
}
}
判断结果:
- 如果只有
execution()能工作 → Spring AOP - 如果
call()、get()、set()等也能工作 → AspectJ
6.2.2 测试内部方法调用
@Service
public class UserService {
public void externalCall() {
internalMethod(); // 内部调用
}
@Loggable // 自定义注解
public void internalMethod() {
System.out.println("内部方法");
}
}
@Aspect
@Component
public class LoggingAspect {
@Before("@annotation(Loggable)")
public void log(JoinPoint jp) {
System.out.println("方法被调用: " + jp.getSignature().getName());
}
}
测试结果:
- 调用
externalCall()时,如果internalMethod的通知没有执行 → Spring AOP - 调用
externalCall()时,如果internalMethod的通知执行了 → AspectJ
6.3. 通过运行时行为判断
6.3.1 检查代理对象类型
@RestController
public class TestController {
@Autowired
private UserService userService;
@GetMapping("/test-proxy")
public String testProxy() {
// 检查是否是代理对象
Class<?> targetClass = userService.getClass();
System.out.println("UserService 实际类型: " + targetClass.getName());
// Spring AOP 的典型输出:
// - JDK代理: com.sun.proxy.$ProxyXX
// - CGLIB代理: com.example.service.UserServiceImpl$$EnhancerBySpringCGLIB$$XXX
// AspectJ (编译时织入) 的输出:
// - com.example.service.UserServiceImpl (原始类名,无代理)
return targetClass.getName();
}
}
判断依据:
- 类名包含
$Proxy或$$EnhancerBySpringCGLIB→ Spring AOP - 类名就是原始类名 → AspectJ(编译时织入)
6.3.2 检查启动日志
Spring AOP 启动日志:
// 通常看不到特殊的AOP相关日志
// 或者只有 Spring 创建 Bean 的日志
AspectJ LTW(类加载时织入)启动日志:
[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.9.x
[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[AppClassLoader@18b4aac2] info using configuration file:/path/to/META-INF/aop.xml
[AppClassLoader@18b4aac2] info register aspect com.example.LoggingAspect
6.4. 通过项目结构判断
Spring AOP 项目结构:
src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── aspect/ # 切面类
│ │ ├── service/ # 业务类
│ │ └── Application.java
└── pom.xml # 只有 spring-boot-starter-aop
AspectJ 编译时织入项目结构:
src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── aspect/ # 切面类
│ │ ├── service/ # 业务类
│ │ └── Application.java
│ └── resources/
│ └── META-INF/
│ └── aop.xml # LTW 配置文件(如果是LTW)
└── pom.xml # 包含 aspectj-maven-plugin
6.5. 总结
快速诊断清单:
| 检查项 | Spring AOP | AspectJ |
|---|---|---|
| 依赖 | 只有 spring-aop | 有 aspectjrt/aspectjweaver |
| 构建插件 | 无 | 可能有 aspectj-maven-plugin |
| JVM参数 | 无特殊参数 | 可能有 -javaagent |
| 代理类名 | 包含 $Proxy 或 $$Enhancer | 原始类名 |
| 非方法连接点 | 不支持 | 支持 |
| 内部方法调用 | 不拦截 | 拦截 |
| 私有方法 | 不支持 | 支持 |
最简单快速的判断方法:
- 看依赖:有
aspectjweaver就可能是 AspectJ - 看类名:运行时打印对象的 class name,有代理标识就是 Spring AOP
- 试功能:尝试写一个字段访问的切点,能用就是 AspectJ
在大多数 Spring Boot 项目中,默认使用的就是 Spring AOP,除非特别配置了 AspectJ。
7. 相关文档
- 面向切面编程(AOP)介绍:https://blog.csdn.net/taotiezhengfeng/article/details/149515395
- 两种AOP实现方式对比:Spring AOP vs AspectJ:https://blog.csdn.net/taotiezhengfeng/article/details/158420122
- Spring AOP原理简析:https://blog.csdn.net/taotiezhengfeng/article/details/155982763
- Spring AOP切点表达式的关键词梳理:https://blog.csdn.net/taotiezhengfeng/article/details/155744545
- SpringAOP通知参数设置规则:https://blog.csdn.net/taotiezhengfeng/article/details/158425675
- AOP切点表达式什么时候使用全限定类名:https://blog.csdn.net/taotiezhengfeng/article/details/158428772
- SpringAOP 是如何进行参数赋值的:https://blog.csdn.net/taotiezhengfeng/article
更多推荐



所有评论(0)