
在 Java 企业级开发中,AOP(面向切面编程)是解决代码横切问题的 "银弹"。无论是日志记录、事务管理还是权限控制,AOP 都能让我们的代码保持整洁与高效。但在实际开发中,很多开发者都会混淆 Spring AOP 和 AspectJ 这两个技术 —— 它们名字相似,功能重叠,甚至连语法都有几分相像。
你是否遇到过这些问题:
本文将从底层原理到实际应用,全方位剖析 Spring AOP 与 AspectJ 的区别,帮你彻底搞懂这两个技术的本质差异,避免在实际开发中踩坑。
在深入探讨两者区别之前,我们先快速回顾 AOP 的核心概念,这是理解后续内容的基础。

Spring AOP 是 Spring 框架的一部分,诞生于 2003 年,设计目标是为 Spring 生态提供简单易用的 AOP 功能,解决企业级开发中常见的横切问题(如事务管理、日志记录等)。
AspectJ 则起源于 1997 年的 PARC 研究中心,是一个独立的 AOP 语言扩展,设计目标是提供完整的 AOP 解决方案,支持所有可能的横切场景。

这是两者最核心的区别,直接决定了它们的功能范围和使用场景。
Spring AOP 基于动态代理机制实现,仅在运行时生成代理对象,具体有两种方式:

AspectJ 通过修改字节码实现 AOP,支持三种织入时机:

由于实现方式的不同,两者的功能范围有显著差异:
功能 | Spring AOP | AspectJ |
|---|---|---|
方法执行连接点 | 支持 | 支持 |
字段访问连接点 | 不支持 | 支持 |
构造器连接点 | 不支持 | 支持 |
静态方法连接点 | 有限支持 | 支持 |
自调用拦截 | 不支持 | 支持 |
切入点表达式 | 支持有限子集 | 支持完整表达式 |
织入时机 | 运行时 | 编译期 / 类加载期 / 运行时 |
性能 | 运行时有额外开销 | 织入后无额外开销 |
Spring AOP 的核心是动态代理,我们通过实例来理解其工作机制。
首先定义一个业务接口:
/**
* 订单服务接口
* @author ken
*/
public interface OrderService {
/**
* 创建订单
* @param userId 用户ID
* @param productId 产品ID
* @return 订单ID
*/
String createOrder(String userId, String productId);
}
实现该接口:
/**
* 订单服务实现类
* @author ken
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String userId, String productId) {
log.info("创建订单:用户ID={}, 产品ID={}", userId, productId);
// 模拟订单创建逻辑
return "ORDER_" + System.currentTimeMillis();
}
}
创建切面类:
/**
* 订单切面类
* @author ken
*/
@Aspect
@Component
@Slf4j
public class OrderAspect {
/**
* 定义切入点:匹配OrderService接口的所有方法
*/
@Pointcut("execution(* com.example.demo.service.OrderService.*(..))")
public void orderPointcut() {}
/**
* 前置通知
* @param joinPoint 连接点
*/
@Before("orderPointcut()")
public void beforeCreateOrder(JoinPoint joinPoint) {
log.info("准备创建订单,参数:{}", Arrays.toString(joinPoint.getArgs()));
}
/**
* 后置通知
* @param result 方法返回值
*/
@AfterReturning(pointcut = "orderPointcut()", returning = "result")
public void afterCreateOrder(Object result) {
log.info("订单创建完成,订单ID:{}", result);
}
}
Spring 配置类:
/**
* Spring配置类
* @author ken
*/
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy(proxyTargetClass = false) // false表示优先使用JDK动态代理
public class AppConfig {
}
测试类:
/**
* 测试类
* @author ken
*/
@Slf4j
public class AopTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
log.info("获取的bean类型:{}", orderService.getClass().getName());
orderService.createOrder("USER_001", "PROD_001");
}
}
运行结果:
获取的bean类型:com.sun.proxy.$Proxy18
准备创建订单,参数:[USER_001, PROD_001]
创建订单:用户ID=USER_001, 产品ID=PROD_001
订单创建完成,订单ID:ORDER_1620000000000
可以看到,Spring 创建的是 JDK 动态代理对象(com.sun.proxy.$Proxy18),而非原始的 OrderServiceImpl。
修改配置类,设置 proxyTargetClass = true:
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy(proxyTargetClass = true) // true表示使用CGLIB代理
public class AppConfig {
}
再次运行测试类,结果:
获取的bean类型:com.example.demo.service.OrderServiceImpl$$EnhancerBySpringCGLIB$$a1b2c3d4
准备创建订单,参数:[USER_001, PROD_001]
创建订单:用户ID=USER_001, 产品ID=PROD_001
订单创建完成,订单ID:ORDER_1620000000000
此时 Spring 创建的是 CGLIB 代理对象(类名包含 EnhancerBySpringCGLIB)。
Spring AOP 无法拦截字段访问、构造器调用等非方法连接点。
当一个类的方法调用同类的另一个方法时,切面不会生效:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String userId, String productId) {
log.info("创建订单:用户ID={}, 产品ID={}", userId, productId);
// 自调用
updateOrderStatus();
return "ORDER_" + System.currentTimeMillis();
}
public void updateOrderStatus() {
log.info("更新订单状态");
}
}
如果我们为 updateOrderStatus () 方法添加切面,当通过 createOrder () 调用时,切面不会生效,因为自调用是通过 this 引用,而非代理对象。
动态代理在每次方法调用时都需要通过代理对象转发,存在一定的性能开销。
AspectJ 提供了三种织入方式,我们重点介绍最常用的编译期织入和类加载期织入。
使用 AspectJ 的 ajc 编译器,在编译时将切面代码直接编译到目标类中。
在类加载时,通过 Java Agent 机制修改字节码,将切面逻辑织入目标类。
AspectJ 支持字段访问、构造器、静态方法等连接点类型:
/**
* 演示各种连接点的切面
* @author ken
*/
@Aspect
public class MultiJoinPointAspect {
// 字段访问连接点
@Before("get(* com.example.demo.model.User.name)")
public void beforeGetName() {
System.out.println("准备获取用户名");
}
// 构造器连接点
@After("execution(* com.example.demo.model.User.new(..))")
public void afterUserConstruct() {
System.out.println("User对象构造完成");
}
// 静态方法连接点
@Around("call(static * com.example.demo.util.DateUtils.format(..))")
public Object aroundFormat(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("准备调用静态方法format");
Object result = joinPoint.proceed();
System.out.println("静态方法format调用完成");
return result;
}
}
由于 AspectJ 是在字节码级别织入切面,自调用方法也能被正常拦截:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String userId, String productId) {
log.info("创建订单:用户ID={}, 产品ID={}", userId, productId);
// 自调用,AspectJ可以拦截
updateOrderStatus();
return "ORDER_" + System.currentTimeMillis();
}
public void updateOrderStatus() {
log.info("更新订单状态");
}
}
使用 AspectJ 时,updateOrderStatus () 的切面会正常生效。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.20.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.20.1</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<source>17</source>
<target>17</target>
<complianceLevel>17</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<aspectj>
<aspects>
<aspect name="com.example.demo.aspect.OrderAspect"/>
</aspects>
<weaver options="-verbose -showWeaveInfo">
<include within="com.example.demo.service.*"/>
</weaver>
</aspectj>
java -javaagent:/path/to/aspectjweaver-1.9.20.1.jar -jar demo.jar
我们通过一个简单的测试来对比两者的性能差异:
/**
* 性能测试类
* @author ken
*/
@Slf4j
public class PerformanceTest {
private static final int LOOP_COUNT = 1000000;
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
// 预热
for (int i = 0; i < 1000; i++) {
orderService.createOrder("USER_" + i, "PROD_" + i);
}
// 测试Spring AOP性能
long start = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
orderService.createOrder("USER_" + i, "PROD_" + i);
}
long springAopTime = System.currentTimeMillis() - start;
log.info("Spring AOP执行{}次耗时:{}ms", LOOP_COUNT, springAopTime);
// 测试AspectJ性能(使用编译期织入)
OrderService aspectJOrderService = new OrderServiceImpl();
start = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
aspectJOrderService.createOrder("USER_" + i, "PROD_" + i);
}
long aspectJTime = System.currentTimeMillis() - start;
log.info("AspectJ执行{}次耗时:{}ms", LOOP_COUNT, aspectJTime);
}
}
测试结果(不同环境会有差异):
Spring AOP执行1000000次耗时:286ms
AspectJ执行1000000次耗时:124ms
可以看到,AspectJ 的性能明显优于 Spring AOP,因为 AspectJ 的切面逻辑已经在编译期织入目标类,避免了动态代理的转发开销。
场景 | 推荐技术 | 原因 |
|---|---|---|
事务管理 | Spring AOP | 与 Spring 无缝集成,满足大部分需求 |
日志记录 | 两者皆可 | 简单场景用 Spring AOP,复杂场景用 AspectJ |
权限控制 | 两者皆可 | 根据复杂度选择 |
性能监控 | AspectJ | 性能开销更小 |
字段访问控制 | AspectJ | Spring AOP 不支持 |
构造器增强 | AspectJ | Spring AOP 不支持 |
自调用拦截 | AspectJ | Spring AOP 不支持 |
在实际项目中,我们可以根据需求混合使用两者:
Spring 提供了 @EnableLoadTimeWeaving 注解,方便在 Spring 项目中启用 AspectJ 的类加载期织入:
@Configuration
@EnableLoadTimeWeaving
public class AspectJConfig {
}
问题:当一个类的方法调用同类的另一个方法时,切面不生效。
解决方案:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private ApplicationContext context;
@Override
public String createOrder(String userId, String productId) {
log.info("创建订单:用户ID={}, 产品ID={}", userId, productId);
// 通过上下文获取代理对象
OrderService proxy = context.getBean(OrderService.class);
proxy.updateOrderStatus();
return "ORDER_" + System.currentTimeMillis();
}
public void updateOrderStatus() {
log.info("更新订单状态");
}
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Override
public String createOrder(String userId, String productId) {
log.info("创建订单:用户ID={}, 产品ID={}", userId, productId);
// 获取当前代理对象
OrderService proxy = (OrderService) AopContext.currentProxy();
proxy.updateOrderStatus();
return "ORDER_" + System.currentTimeMillis();
}
public void updateOrderStatus() {
log.info("更新订单状态");
}
}
需要在 @EnableAspectJAutoProxy 中设置 exposeProxy = true:
@EnableAspectJAutoProxy(exposeProxy = true)
通过 AspectJ 的字节码织入,可以从根本上解决自调用问题。
问题:定义的切入点表达式没有匹配到预期的方法。
解决方案:
问题:当多个切面应用到同一个连接点时,需要控制它们的执行顺序。
解决方案:
@Aspect
@Component
@Order(1) // 数值越小,优先级越高
public class FirstAspect {
// ...
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
// ...
}
@Aspect
public class FirstAspect {
// ...
}
@Aspect
public class SecondAspect {
declare precedence: FirstAspect, SecondAspect;
// ...
}
通过本文的学习,相信你已经对 Spring AOP 和 AspectJ 的区别有了深入的理解。在实际开发中,应根据具体需求选择合适的技术,充分发挥它们的优势,同时避免踩坑。记住,没有最好的技术,只有最适合的技术。