
核心思想:动态代理 (Dynamic Proxy)
Spring AOP 的实现原理不是通过修改你编写的 Java 源代码或 .class 文件,而是通过在运行时动态地创建一个代理对象 (Proxy) 来实现的。
这个代理对象“包裹”了你的原始目标对象 (Target Object),并拦截对目标对象方法的调用。当外部代码调用这个代理对象的方法时,代理对象就会在调用真正的目标方法前后,插入你的切面逻辑(即通知 Advice)。
整个过程对调用方是透明的,调用方以为自己拿到的就是原始对象,但实际上它拿到的是功能被增强了的代理对象。
两种主要的实现方式
Spring AOP 根据你的目标对象是否实现了接口,会自动选择两种不同的动态代理技术:
- JDK 动态代理 (JDK Dynamic Proxy)
- CGLIB 代理 (Code Generation Library)
1. JDK 动态代理
-
使用条件:目标对象必须实现至少一个接口。
-
实现原理:
- Spring AOP 使用 Java
java.lang.reflect.Proxy类和InvocationHandler接口来创建代理对象。 - 这个代理对象会实现目标对象所实现的所有接口。
- 当你通过代理对象调用一个方法时,这个调用会被转发到
InvocationHandler的invoke()方法中。 - 在
invoke()方法内部,Spring AOP 框架会根据切点(Pointcut)和通知(Advice)的配置,决定是执行前置通知、调用目标方法、执行后置通知,还是处理异常等。
- Spring AOP 使用 Java
-
优点:
- 是 Java 原生技术,无需引入第三方库。
-
缺点:
- 只能代理实现了接口的类。如果一个类没有实现任何接口,JDK 动态代理就无能为力。
流程图 (JDK 动态代理):
+----------------+ 实现了 +---------------+
| Target Object |<-----------------| SomeInterface|
| (UserService) | +---------------+
+----------------+ ^
| 实现了
|
+-------------------------------------------------+
| Proxy Object (由 Spring 在运行时创建) |
| - 实现了 SomeInterface |
| - 内部持有一个 InvocationHandler (包含切面逻辑) |
| - 内部持有一个对 Target Object 的引用 |
+-------------------------------------------------+
2. CGLIB 代理
-
使用条件:目标对象没有实现任何接口(或者你强制配置使用 CGLIB)。
-
实现原理:
- CGLIB 是一个强大的第三方代码生成库。它通过字节码增强技术 (Bytecode Enhancement) 来工作。
- 它会在运行时动态地创建一个目标对象的子类作为代理对象。
- 然后,它会重写 (Override) 目标对象中所有非 final 的方法。
- 在这些被重写的子类方法中,它会插入你的切面逻辑(类似于 JDK 代理中的
invoke方法回调),然后再通过super.method()调用真正的父类(即目标对象)的业务逻辑。
-
优点:
- 可以代理没有实现接口的类,适用范围更广。
- 在某些场景下,性能可能比 JDK 代理略好(因为避免了反射)。
-
缺点:
- 无法代理
final方法或final类,因为子类无法重写final方法或继承final类。
- 无法代理
流程图 (CGLIB 代理):
+----------------+
| Target Object |
| (OrderService) | <-- 被继承
+----------------+
^
|
+-------------------------------------------------+
| Proxy Object (由 Spring 在运行时创建) |
| - 是 Target Object 的一个子类 |
| - 重写了 Target 中的非 final 方法 |
| - 内部持有一个 MethodInterceptor (包含切面逻辑) |
+-------------------------------------------------+
Spring 如何选择?
- 传统 Spring/老版本 Spring Boot:如果目标对象实现了接口,默认使用 JDK 动态代理;如果没有,则使用 CGLIB。
- 现代 Spring Boot (2.x 及以后):为了统一行为和解决一些复杂场景(如类内部调用),默认统一使用 CGLIB 代理,无论目标对象是否实现接口。你也可以通过配置文件
spring.aop.proxy-target-class=false来改回使用 JDK 代理(如果实现了接口)。
Spring AOP 的完整工作流程
-
启动与扫描:Spring 容器启动时,会扫描所有的 Bean 定义和
@Aspect注解的切面类。 -
寻找切点:Spring 解析每个切面中的切点表达式(Pointcut)。
-
创建代理:在 Bean 的实例化过程中,Spring 会检查每个 Bean 是否匹配任何切点。
- 如果一个 Bean 的方法(连接点)匹配了某个切点,Spring 就判定这个 Bean 需要被增强(“advised”)。
- Spring 不会直接返回这个原始的 Bean 实例,而是会使用 ProxyFactory(代理工厂)为它创建一个代理对象(根据上述规则选择 JDK 或 CGLIB)。
- 这个代理对象包含了所有匹配的切面逻辑(通知)和一个对原始目标对象的引用。
-
注入代理:当其他 Bean 需要依赖这个被增强的 Bean 时(例如通过
@Autowired),Spring 容器注入的是那个代理对象,而不是原始的目标对象。 -
方法调用与拦截:
- 当外部代码调用代理对象的方法时,调用被代理对象拦截。
- 代理对象中的拦截器(
InvocationHandler或MethodInterceptor)被激活。 - 拦截器根据配置,按顺序执行切面逻辑(
@Before,@Around等),并在合适的时机通过反射或super调用来执行原始目标对象的核心业务方法。 - 执行完毕后,将结果返回给调用方。
总结
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 核心技术 | Java 反射 (java.lang.reflect.Proxy) | 字节码增强 (Bytecode Enhancement) |
| 代理方式 | 实现目标对象的所有接口 | 继承目标对象,创建其子类 |
| 前提条件 | 目标对象必须实现接口 | 目标对象不能是 final 类,方法不能是 final |
| Spring Boot 默认 | 否 | 是 (推荐) |
| 对代码影响 | 无 | 无 |
理解 Spring AOP 是基于运行时动态代理这一核心原理,以及它与目标对象和代理对象的关系,是掌握 AOP 并解决类似“方法内部调用导致 AOP 失效”等问题的关键。
3323

被折叠的 条评论
为什么被折叠?



