面向切面编程(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 基于代理模式,在运行时动态创建代理对象。

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 依赖
      • 不需要 aspectjrtaspectjweaver 等 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 的典型特征:

      • 包含 aspectjrtaspectjweaver 等依赖
      • 可能有 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 原始类名
      非方法连接点 不支持 支持
      内部方法调用 不拦截 拦截
      私有方法 不支持 支持

      最简单快速的判断方法:

      1. 看依赖:有 aspectjweaver 就可能是 AspectJ
      2. 看类名:运行时打印对象的 class name,有代理标识就是 Spring AOP
      3. 试功能:尝试写一个字段访问的切点,能用就是 AspectJ

      在大多数 Spring Boot 项目中,默认使用的就是 Spring AOP,除非特别配置了 AspectJ。

      7. 相关文档

      1.  面向切面编程(AOP)介绍:https://blog.csdn.net/taotiezhengfeng/article/details/149515395
      2. 两种AOP实现方式对比:Spring AOP vs AspectJ:https://blog.csdn.net/taotiezhengfeng/article/details/158420122
      3. Spring AOP原理简析:https://blog.csdn.net/taotiezhengfeng/article/details/155982763
      4. Spring AOP切点表达式的关键词梳理:https://blog.csdn.net/taotiezhengfeng/article/details/155744545
      5. SpringAOP通知参数设置规则:https://blog.csdn.net/taotiezhengfeng/article/details/158425675
      6. AOP切点表达式什么时候使用全限定类名:https://blog.csdn.net/taotiezhengfeng/article/details/158428772
      7. SpringAOP 是如何进行参数赋值的:https://blog.csdn.net/taotiezhengfeng/article

      Logo

      汇聚全球AI编程工具,助力开发者即刻编程。

      更多推荐