Skip to content

AOP 代理 (AOP Proxies)

Spring AOP 默认使用标准的 JDK 动态代理 作为 AOP 代理。这使得任何接口(或一组接口)都可以被代理。

Spring AOP 也可以使用 CGLIB 代理。当需要代理类而不是接口时,CGLIB 是必需的。默认情况下,如果业务对象没有实现任何接口,则会使用 CGLIB。由于“面向接口编程”而非“面向类编程”是良好的实践,因此业务类通常会实现一个或多个业务接口。但在某些(希望是少数)情况下,如果你需要通知(Advise)一个未在接口上声明的方法,或者需要将代理对象作为具体类型传递给方法,则可以强制使用 CGLIB

理解“Spring AOP 是基于代理的”这一事实至关重要。请参阅理解 AOP 代理,以深入了解这一实现细节的具体含义。


补充教学

1. JDK 动态代理 vs. CGLIB 代理:双雄会

在 Spring AOP 的底层,代理模式是其灵魂。以下是两者的核心差异:

特性JDK 动态代理CGLIB 代理
原理利用反射机制生成一个实现代理接口的匿名类。利用 ASM 字节码操作框架,生成目标类的子类。
限制必须实现接口不能是 final 类,且不能拦截 final 方法
Spring 默认行为如果目标类实现了接口,默认用它。如果目标类没实现接口,自动切换到它。
性能在现代 JDK 中性能已经非常出色。历史上性能略高于 JDK,但现在差异极小。

2. 为什么推荐“面向接口编程”?

Spring 默认倾向于 JDK 动态代理,是因为它更符合装饰器模式和依赖倒置原则。使用接口作为代理边界,可以确保你的代码与具体的实现类解耦,即便底层实现发生了变化(比如从 MyServiceImpl 换成 NewServiceImpl),调用方也不受影响。

3. 如何强制使用 CGLIB?

如果你明明有接口,但出于某些特殊原因(比如需要强转为实现类)非要用 CGLIB,可以进行如下配置:

  • Java 配置 (Spring Boot)
    properties
    # 在 application.properties 中
    spring.aop.proxy-target-class=true
  • 注解配置
    java
    @EnableAspectJAutoProxy(proxyTargetClass = true)

4. 一个致命的陷阱:finalprivate

  • CGLIB 的软肋:既然 CGLIB 是通过生成子类来重写父类方法实现的,那么 final 方法是无法被子类重写的,因此 AOP 对 final 方法无效。
  • 共同限制:无论是 JDK 还是 CGLIB,都无法代理 static 方法。此外,Spring AOP 由于是通过代理调用的,因此 private 方法也无法被拦截。

5. 运行时如何确认代理类型?

你在调试时想知道某个 Bean 到解是哪种代理?可以通过代码查看:

java
// 使用 AopUtils 工具类
System.out.println("是否是 JDK 代理: " + AopUtils.isJdkDynamicProxy(myBean));
System.out.println("是否是 CGLIB 代理: " + AopUtils.isCglibProxy(myBean));

或者在调试器(Debugger)中观察对象的类名:

  • JDK 代理通常包含 $Proxy 字样。
  • CGLIB 代理通常包含 $$EnhancerBySpringCGLIB$$ 字样。

Based on Spring Framework.