首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从入门到精通:Spring AOP 与 AspectJ 的 "爱恨情仇"——90% 开发者都踩过的坑全解析

从入门到精通:Spring AOP 与 AspectJ 的 "爱恨情仇"——90% 开发者都踩过的坑全解析

作者头像
果酱带你啃java
发布2026-04-14 10:11:28
发布2026-04-14 10:11:28
380
举报

前言:为什么你必须搞懂这两者的区别?

在 Java 企业级开发中,AOP(面向切面编程)是解决代码横切问题的 "银弹"。无论是日志记录、事务管理还是权限控制,AOP 都能让我们的代码保持整洁与高效。但在实际开发中,很多开发者都会混淆 Spring AOP 和 AspectJ 这两个技术 —— 它们名字相似,功能重叠,甚至连语法都有几分相像。

你是否遇到过这些问题:

  • 为什么同样的 @Before 注解,有时候能拦截接口调用,有时候却不行?
  • 为什么使用 Spring AOP 时,自调用方法的切面不生效?
  • 为什么引入 AspectJ 后,项目启动速度变慢了?

本文将从底层原理到实际应用,全方位剖析 Spring AOP 与 AspectJ 的区别,帮你彻底搞懂这两个技术的本质差异,避免在实际开发中踩坑。

一、AOP 核心概念快速回顾

在深入探讨两者区别之前,我们先快速回顾 AOP 的核心概念,这是理解后续内容的基础。

1.1 核心术语解析

  • 连接点 (Join Point):程序执行过程中的一个点,如方法调用、字段赋值等。在 Spring AOP 中,连接点仅支持方法执行。
  • 切点 (Pointcut):用于匹配连接点的表达式,决定了哪些连接点会被拦截。
  • 通知 (Advice):在切点匹配的连接点执行的代码,包括前置通知 (@Before)、后置通知 (@After)、返回通知 (@AfterReturning)、异常通知 (@AfterThrowing) 和环绕通知 (@Around)。
  • 切面 (Aspect):切点和通知的组合,定义了横切逻辑的完整模块。
  • 织入 (Weaving):将切面应用到目标对象并创建代理对象的过程,根据时机可分为编译期织入、类加载期织入和运行期织入。

二、Spring AOP 与 AspectJ 的本质区别

2.1 出身背景与设计目标

Spring AOP 是 Spring 框架的一部分,诞生于 2003 年,设计目标是为 Spring 生态提供简单易用的 AOP 功能,解决企业级开发中常见的横切问题(如事务管理、日志记录等)。

AspectJ 则起源于 1997 年的 PARC 研究中心,是一个独立的 AOP 语言扩展,设计目标是提供完整的 AOP 解决方案,支持所有可能的横切场景。

2.2 实现方式的根本差异

这是两者最核心的区别,直接决定了它们的功能范围和使用场景。

Spring AOP:动态代理实现

Spring AOP 基于动态代理机制实现,仅在运行时生成代理对象,具体有两种方式:

  • 当目标类实现接口时,使用 JDK 动态代理
  • 当目标类未实现接口时,使用 CGLIB 代理(Spring 5.2 + 默认使用 CGLIB)
AspectJ:字节码增强实现

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

  • 编译期织入:在编译时直接将切面代码编译到目标类字节码中
  • 类加载期织入:在类加载时修改字节码
  • 运行期织入:在运行时动态修改字节码(较少使用)

2.3 功能范围对比

由于实现方式的不同,两者的功能范围有显著差异:

功能

Spring AOP

AspectJ

方法执行连接点

支持

支持

字段访问连接点

不支持

支持

构造器连接点

不支持

支持

静态方法连接点

有限支持

支持

自调用拦截

不支持

支持

切入点表达式

支持有限子集

支持完整表达式

织入时机

运行时

编译期 / 类加载期 / 运行时

性能

运行时有额外开销

织入后无额外开销

三、Spring AOP 深度剖析

3.1 底层实现原理

Spring AOP 的核心是动态代理,我们通过实例来理解其工作机制。

JDK 动态代理实例

首先定义一个业务接口:

代码语言:javascript
复制
/**
 * 订单服务接口
 * @author ken
 */
public interface OrderService {
    /**
     * 创建订单
     * @param userId 用户ID
     * @param productId 产品ID
     * @return 订单ID
     */
    String createOrder(String userId, String productId);
}
代码语言:javascript
复制

实现该接口:

代码语言:javascript
复制
/**
 * 订单服务实现类
 * @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();
    }
}
代码语言:javascript
复制

创建切面类:

代码语言:javascript
复制
/**
 * 订单切面类
 * @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);
    }
}
代码语言:javascript
复制

Spring 配置类:

代码语言:javascript
复制
/**
 * Spring配置类
 * @author ken
 */
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy(proxyTargetClass = false) // false表示优先使用JDK动态代理
public class AppConfig {
}
代码语言:javascript
复制

测试类:

代码语言:javascript
复制
/**
 * 测试类
 * @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");
    }
}
代码语言:javascript
复制

运行结果:

代码语言:javascript
复制
获取的bean类型:com.sun.proxy.$Proxy18
准备创建订单,参数:[USER_001, PROD_001]
创建订单:用户ID=USER_001, 产品ID=PROD_001
订单创建完成,订单ID:ORDER_1620000000000
代码语言:javascript
复制

可以看到,Spring 创建的是 JDK 动态代理对象(com.sun.proxy.$Proxy18),而非原始的 OrderServiceImpl。

CGLIB 代理实例

修改配置类,设置 proxyTargetClass = true:

代码语言:javascript
复制
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy(proxyTargetClass = true) // true表示使用CGLIB代理
public class AppConfig {
}
代码语言:javascript
复制

再次运行测试类,结果:

代码语言:javascript
复制
获取的bean类型:com.example.demo.service.OrderServiceImpl$$EnhancerBySpringCGLIB$$a1b2c3d4
准备创建订单,参数:[USER_001, PROD_001]
创建订单:用户ID=USER_001, 产品ID=PROD_001
订单创建完成,订单ID:ORDER_1620000000000
代码语言:javascript
复制

此时 Spring 创建的是 CGLIB 代理对象(类名包含 EnhancerBySpringCGLIB)。

3.2 Spring AOP 的局限性

  1. 仅支持方法级别的连接点

Spring AOP 无法拦截字段访问、构造器调用等非方法连接点。

  1. 自调用问题

当一个类的方法调用同类的另一个方法时,切面不会生效:

代码语言:javascript
复制
@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("更新订单状态");
    }
}
代码语言:javascript
复制

如果我们为 updateOrderStatus () 方法添加切面,当通过 createOrder () 调用时,切面不会生效,因为自调用是通过 this 引用,而非代理对象。

  1. 性能开销

动态代理在每次方法调用时都需要通过代理对象转发,存在一定的性能开销。

四、AspectJ 深度剖析

4.1 实现原理与织入方式

AspectJ 提供了三种织入方式,我们重点介绍最常用的编译期织入和类加载期织入。

编译期织入

使用 AspectJ 的 ajc 编译器,在编译时将切面代码直接编译到目标类中。

类加载期织入

在类加载时,通过 Java Agent 机制修改字节码,将切面逻辑织入目标类。

4.2 AspectJ 的强大功能

支持更多连接点类型

AspectJ 支持字段访问、构造器、静态方法等连接点类型:

代码语言:javascript
复制
/**
 * 演示各种连接点的切面
 * @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;
    }
}
代码语言:javascript
复制

解决自调用问题

由于 AspectJ 是在字节码级别织入切面,自调用方法也能被正常拦截:

代码语言:javascript
复制
@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("更新订单状态");
    }
}
代码语言:javascript
复制

使用 AspectJ 时,updateOrderStatus () 的切面会正常生效。

4.3 AspectJ 的使用方式

方式一:纯 AspectJ(使用 ajc 编译器)
  1. 添加 Maven 依赖:
代码语言:javascript
复制
<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>
代码语言:javascript
复制

  1. 配置 ajc 编译器插件:
代码语言:javascript
复制
<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>
代码语言:javascript
复制

方式二:Spring 整合 AspectJ(类加载期织入)
  1. 添加 Maven 依赖(同上)
  2. 创建 aop.xml 配置文件(src/main/resources/META-INF/aop.xml):
代码语言:javascript
复制
<aspectj>
    <aspects>
        <aspect name="com.example.demo.aspect.OrderAspect"/>
    </aspects>
    <weaver options="-verbose -showWeaveInfo">
        <include within="com.example.demo.service.*"/>
    </weaver>
</aspectj>
代码语言:javascript
复制

  1. 使用 Java Agent 启动应用:
代码语言:javascript
复制
java -javaagent:/path/to/aspectjweaver-1.9.20.1.jar -jar demo.jar
代码语言:javascript
复制

五、Spring AOP 与 AspectJ 的实战对比

5.1 性能对比

我们通过一个简单的测试来对比两者的性能差异:

代码语言:javascript
复制
/**
 * 性能测试类
 * @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);
    }
}
代码语言:javascript
复制

测试结果(不同环境会有差异):

代码语言:javascript
复制
Spring AOP执行1000000次耗时:286ms
AspectJ执行1000000次耗时:124ms
代码语言:javascript
复制

可以看到,AspectJ 的性能明显优于 Spring AOP,因为 AspectJ 的切面逻辑已经在编译期织入目标类,避免了动态代理的转发开销。

5.2 使用场景对比

场景

推荐技术

原因

事务管理

Spring AOP

与 Spring 无缝集成,满足大部分需求

日志记录

两者皆可

简单场景用 Spring AOP,复杂场景用 AspectJ

权限控制

两者皆可

根据复杂度选择

性能监控

AspectJ

性能开销更小

字段访问控制

AspectJ

Spring AOP 不支持

构造器增强

AspectJ

Spring AOP 不支持

自调用拦截

AspectJ

Spring AOP 不支持

5.3 混合使用策略

在实际项目中,我们可以根据需求混合使用两者:

  1. 大部分场景使用 Spring AOP,简单易用
  2. 对性能要求高或需要特殊连接点的场景使用 AspectJ

Spring 提供了 @EnableLoadTimeWeaving 注解,方便在 Spring 项目中启用 AspectJ 的类加载期织入:

代码语言:javascript
复制
@Configuration
@EnableLoadTimeWeaving
public class AspectJConfig {
}
代码语言:javascript
复制

六、常见问题与解决方案

6.1 Spring AOP 的自调用问题

问题:当一个类的方法调用同类的另一个方法时,切面不生效。

解决方案

  1. 方案一:通过 ApplicationContext 获取代理对象
代码语言:javascript
复制
@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("更新订单状态");
    }
}
代码语言:javascript
复制

  1. 方案二:使用 AopContext.currentProxy ()
代码语言:javascript
复制
@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("更新订单状态");
    }
}
代码语言:javascript
复制

需要在 @EnableAspectJAutoProxy 中设置 exposeProxy = true:

代码语言:javascript
复制
@EnableAspectJAutoProxy(exposeProxy = true)
代码语言:javascript
复制

  1. 方案三:改用 AspectJ

通过 AspectJ 的字节码织入,可以从根本上解决自调用问题。

6.2 切入点表达式不生效

问题:定义的切入点表达式没有匹配到预期的方法。

解决方案

  1. 检查表达式语法,确保符合规范
  2. 注意访问修饰符:execution (public * (..)) 与 execution ( *(..)) 不同
  3. 注意包名和类名的正确性
  4. 使用 Spring 的 @Pointcut 注解时,确保切面类被 Spring 管理
  5. 对于 AspectJ,检查织入配置是否正确

6.3 多个切面的执行顺序

问题:当多个切面应用到同一个连接点时,需要控制它们的执行顺序。

解决方案

  1. Spring AOP:使用 @Order 注解
代码语言:javascript
复制
@Aspect
@Component
@Order(1) // 数值越小,优先级越高
public class FirstAspect {
    // ...
}

@Aspect
@Component
@Order(2)
public class SecondAspect {
    // ...
}
代码语言:javascript
复制

  1. AspectJ:使用 declare precedence
代码语言:javascript
复制
@Aspect
public class FirstAspect {
    // ...
}

@Aspect
public class SecondAspect {
    declare precedence: FirstAspect, SecondAspect;
    // ...
}
代码语言:javascript
复制

七、总结与最佳实践

7.1 核心区别总结

  1. 实现方式:Spring AOP 基于动态代理,AspectJ 基于字节码增强
  2. 功能范围:AspectJ 支持更全面的连接点类型
  3. 性能:AspectJ 在织入后性能更优
  4. 易用性:Spring AOP 更简单,与 Spring 生态无缝集成
  5. 适用场景:简单场景用 Spring AOP,复杂场景用 AspectJ

7.2 最佳实践建议

  1. 优先使用 Spring AOP:对于大多数企业级应用,Spring AOP 已经足够满足需求,且使用简单,与 Spring 生态无缝集成。
  2. 关键路径考虑 AspectJ:在性能敏感的关键路径上,考虑使用 AspectJ 以获得更好的性能。
  3. 混合使用策略:大部分场景使用 Spring AOP,特殊需求(如字段访问、自调用拦截)使用 AspectJ。
  4. 避免过度使用 AOP:AOP 虽然强大,但过度使用会增加代码复杂度和调试难度,应谨慎使用。
  5. 统一切面风格:在项目中保持一致的切面风格,避免同时使用多种 AOP 实现方式导致混乱。

通过本文的学习,相信你已经对 Spring AOP 和 AspectJ 的区别有了深入的理解。在实际开发中,应根据具体需求选择合适的技术,充分发挥它们的优势,同时避免踩坑。记住,没有最好的技术,只有最适合的技术。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:为什么你必须搞懂这两者的区别?
  • 一、AOP 核心概念快速回顾
    • 1.1 核心术语解析
  • 二、Spring AOP 与 AspectJ 的本质区别
    • 2.1 出身背景与设计目标
    • 2.2 实现方式的根本差异
      • Spring AOP:动态代理实现
      • AspectJ:字节码增强实现
    • 2.3 功能范围对比
  • 三、Spring AOP 深度剖析
    • 3.1 底层实现原理
      • JDK 动态代理实例
      • CGLIB 代理实例
    • 3.2 Spring AOP 的局限性
  • 四、AspectJ 深度剖析
    • 4.1 实现原理与织入方式
      • 编译期织入
      • 类加载期织入
    • 4.2 AspectJ 的强大功能
      • 支持更多连接点类型
      • 解决自调用问题
    • 4.3 AspectJ 的使用方式
      • 方式一:纯 AspectJ(使用 ajc 编译器)
      • 方式二:Spring 整合 AspectJ(类加载期织入)
  • 五、Spring AOP 与 AspectJ 的实战对比
    • 5.1 性能对比
    • 5.2 使用场景对比
    • 5.3 混合使用策略
  • 六、常见问题与解决方案
    • 6.1 Spring AOP 的自调用问题
    • 6.2 切入点表达式不生效
    • 6.3 多个切面的执行顺序
  • 七、总结与最佳实践
    • 7.1 核心区别总结
    • 7.2 最佳实践建议
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档