Spring AOP

2025/05/06 Spring 共 3533 字,约 11 分钟

Spring AOP

谈谈自己对于 AOP 的了解

AOP,也就是面向切面编程,简单点说,AOP 就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

AOP 切面编程涉及到的一些专业术语:

术语含义
目标(Target)被通知的对象
代理(Proxy)向目标对象应用通知之后创建的代理对象
连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut)被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice)增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect)切入点(Pointcut)+通知(Advice)
Weaving(织入)将通知应用到目标对象,进而生成代理对象的过程动作
Spring AOP
   ├── 使用 AspectJ 注解(如 @Aspect)来声明切面。 它是单独的aop框架,可以在非spring里使用。和spring aop是两套。 它是编译时织入。
   ├── 使用代理方式实现 AOP:
   │     ├── JDK 动态代理(接口型)
   │     └── CGLIB 动态代理(类型)

AOP 常见的通知类型有哪些?

AOP 一般有 5 种环绕方式:

  • 前置通知 (@Before)
  • 返回通知 (@AfterReturning)
  • 异常通知 (@AfterThrowing)
  • 后置通知 (@After)
  • 环绕通知 (@Around)

三分恶面渣逆袭:环绕方式

AspectJ 是什么?

AspectJ

AspectJ 是一个 AOP 框架,它可以做很多 Spring AOP 干不了的事情,比如说支持编译时、编译后和类加载时织入切面。并且提供更复杂的切点表达式和通知类型。

@Aspect  // 表示这是一个切面类
@Component  // Spring 管理该类的 Bean
public class LoggingAspect {

    // 定义一个切入点,匹配 HelloService 中的所有方法
    @Pointcut("execution(* com.example.demo.HelloService.*(..))")
    public void helloServiceMethods() {}

    // 方法执行之前的通知(Before Advice)
    @Before("helloServiceMethods()")
    public void logBefore() {
        System.out.println("Method is about to execute");
    }

    // 方法执行之后的通知(After Advice)
    @After("helloServiceMethods()")
    public void logAfter() {
        System.out.println("Method has executed");
    }
}

Spring AOP 和 AspectJ AOP 有什么区别?

特性Spring AOPAspectJ
增强方式运行时增强(基于动态代理)编译时增强、类加载时增强(直接操作字节码)
切入点支持方法级(Spring Bean 范围内,不支持 final 和 staic 方法)方法级、字段、构造器、静态方法等
性能运行时依赖代理,有一定开销,切面多时性能较低运行时无代理开销,性能更高
复杂性简单,易用,适合大多数场景功能强大,但相对复杂
使用场景Spring 应用下比较简单的 AOP 需求高性能、高复杂度的 AOP 需求

如何选择?

  • 功能考量:AspectJ 支持更复杂的 AOP 场景,Spring AOP 更简单易用。如果你需要增强 final 方法、静态方法、字段访问、构造器调用等,或者需要在非 Spring 管理的对象上应用增强逻辑,AspectJ 是唯一的选择。
  • 性能考量:切面数量较少时两者性能差异不大,但切面较多时 AspectJ 性能更优。

一句话总结:简单场景优先使用 Spring AOP;复杂场景或高性能需求时,选择 AspectJ。

Spring AOP 属于运行时增强,主要具有如下特点:

  1. 基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现
  2. Spring AOP 需要依赖 IoC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现
  3. 在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。
  4. Spring AOP 致力于解决企业级开发中最普遍的 AOP(方法织入)。

AspectJ 是一个易用的功能强大的 AOP 框架,属于编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。

AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:

  1. 编译期织入(Compile-time weaving):如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
  2. 编译后织入(Post-compile weaving):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
  3. 类加载后织入(Load-time weaving):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法

Sprint AOP

Spring AOP 发生在什么时候?

Spring AOP 基于运行时代理机制,这意味着 Spring AOP 是在运行时通过动态代理生成的,而不是在编译时或类加载时生成的。

在 Spring 容器初始化 Bean 的过程中,Spring AOP 会检查 Bean 是否需要应用切面。如果需要,Spring 会为该 Bean 创建一个代理对象,并在代理对象中织入切面逻辑。这一过程发生在 Spring 容器的后处理器(BeanPostProcessor)阶段。

说说 JDK 动态代理和 CGLIB 代理?

AOP 是通过动态代理实现的,代理方式有两种:JDK 动态代理和 CGLIB 代理。

①、JDK 动态代理是基于接口的代理,只能代理实现了接口的类。

使用 JDK 动态代理时,Spring AOP 会创建一个代理对象,该代理对象实现了目标对象所实现的接口,并在方法调用前后插入横切逻辑。

优点:只需依赖 JDK 自带的 java.lang.reflect.Proxy 类,不需要额外的库;缺点:只能代理接口,不能代理类本身。

②、CGLIB 动态代理是基于继承的代理,可以代理没有实现接口的类。

使用 CGLIB 动态代理时,Spring AOP 会生成目标类的子类,并在方法调用前后插入横切逻辑。

优点:可以代理没有实现接口的类,灵活性更高;缺点:需要依赖 CGLIB 库,创建代理对象的开销相对较大。

JDK 动态代理只能代理实现了接口的类。它的实现基于 Java 的 java.lang.reflect.Proxy 类和 InvocationHandler 接口,运行时在内存中动态生成代理类的 .class 文件并加载执行。

JDK Proxy 是通过在内存中动态创建一个实现了同样接口的代理类来“代理”目标对象的方法调用。

比较维度JDK 动态代理CGLIB 动态代理
代理对象必须实现接口不需要接口
实现方式JDK Proxy + 反射继承 + 字节码增强
性能(早期)较低较高
限制不能代理类本身不能代理 final 类/方法
Spring 默认策略有接口:JDK 动态代理无接口:CGLIB 

选择 CGLIB 还是 JDK 动态代理?

  • 如果目标对象没有实现任何接口,则只能使用 CGLIB 代理。如果目标对象实现了接口,通常首选 JDK 动态代理。
  • 虽然 CGLIB 在代理类的生成过程中可能消耗更多资源,但在运行时具有较高的性能。对于性能敏感且代理对象创建频率不高的场景,可以考虑使用 CGLIB。
  • JDK 动态代理是 Java 原生支持的,不需要额外引入库。而 CGLIB 需要将 CGLIB 库作为依赖加入项目中。

Search

    Table of Contents