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 AOP | AspectJ |
|---|---|---|
| 增强方式 | 运行时增强(基于动态代理) | 编译时增强、类加载时增强(直接操作字节码) |
| 切入点支持 | 方法级(Spring Bean 范围内,不支持 final 和 staic 方法) | 方法级、字段、构造器、静态方法等 |
| 性能 | 运行时依赖代理,有一定开销,切面多时性能较低 | 运行时无代理开销,性能更高 |
| 复杂性 | 简单,易用,适合大多数场景 | 功能强大,但相对复杂 |
| 使用场景 | Spring 应用下比较简单的 AOP 需求 | 高性能、高复杂度的 AOP 需求 |
如何选择?
- 功能考量:AspectJ 支持更复杂的 AOP 场景,Spring AOP 更简单易用。如果你需要增强
final方法、静态方法、字段访问、构造器调用等,或者需要在非 Spring 管理的对象上应用增强逻辑,AspectJ 是唯一的选择。 - 性能考量:切面数量较少时两者性能差异不大,但切面较多时 AspectJ 性能更优。
一句话总结:简单场景优先使用 Spring AOP;复杂场景或高性能需求时,选择 AspectJ。
Spring AOP 属于运行时增强,主要具有如下特点:
- 基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现
- Spring AOP 需要依赖 IoC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现
- 在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。
- Spring AOP 致力于解决企业级开发中最普遍的 AOP(方法织入)。
AspectJ 是一个易用的功能强大的 AOP 框架,属于编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。
AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:
- 编译期织入(Compile-time weaving):如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- 编译后织入(Post-compile weaving):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- 类加载后织入(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 库作为依赖加入项目中。