代理机制
Spring AOP使用JDK动态代理或CGLIB来为给定的目标对象创建代理。JDK动态代理是JDK内置的,而CGLIB是一个常见的开源类定义库(被重新打包到spring-core中)。
如果目标对象实现了至少一个接口,则使用JDK动态代理,目标类型实现的所有接口都将被代理。如果目标对象没有实现任何接口,则会创建一个CGLIB代理,该代理是目标类型的运行时生成的子类。
如果你想强制使用CGLIB代理(例如,代理目标对象定义的每一个方法,而不仅仅是其接口实现的方法),这是可以的。然而,你应该考虑以下问题:
final类不能被代理,因为它们不能被扩展。final方法不能被通知(advised),因为它们不能被重写。private方法不能被通知,因为它们不能被重写。- 那些不可见的方法——例如,来自不同包的父类中的包私有(package-private)方法——也不能被通知,因为它们实际上是被私有的(private)。
- 你的被代理对象的构造函数不会被调用两次,因为 CGLIB 代理实例是通过 Objenesis 创建的。然而,如果你的 JVM 不允许绕过构造函数,你可能会看到两次调用以及 Spring AOP 支持产生的相应调试日志条目。
- 使用 CGLIB 代理时可能会遇到 Java 模块系统(Java Module System)的限制。一个典型的例子是,当在模块路径(module path)上部署时,你不能为
java.lang包中的类创建 CGLIB 代理。这种情况下需要使用 JVM 启动参数--add-opens=java.base/java.lang=ALL-UNNAMED,但该参数不适用于模块。
强制使用特定的AOP代理类型
要强制使用CGLIB代理,请将<aop:config>元素的proxy-target-class属性的值设置为true,如下所示:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
要在使用@AspectJ自动代理支持时强制使用CGLIB代理,请将<aop:aspectj-autoproxy>元素的proxy-target-class属性设置为true,如下所示:
<aop:aspectj-autoproxy proxy-target-class="true"/>
在运行时,多个 <aop:config/> 部分会合并为单个统一的自动代理创建器,该创建器将应用所有 <aop:config/> 部分(通常来自不同的 XML bean 定义文件)中指定的最强效的代理设置。这一规则同样适用于 <tx:annotation-driven/> 和 <aop:aspectj-autoproxy/> 元素。
需要明确的是,在 <tx:annotation-driven/>、<aop:aspectj-autoproxy/> 或 <aop:config/> 元素上使用 proxy-target-class="true" 会强制对这三个元素都使用 CGLIB 代理。
@EnableAspectJAutoProxy、@EnableTransactionManagement以及相关的配置注解都提供了相应的proxyTargetClass属性。这些属性也被整合到了一个统一的自动代理创建器中,从而在运行时有效地应用了“最强”的代理设置。从Spring框架7.0版本开始,这一机制也适用于单个代理处理器(如@EnableAsync),它们会一致地参与到给定应用程序中所有自动代理尝试的统一全局默认设置中。
不同设置下,全局默认的代理类型可能会有所不同。虽然核心框架默认推荐使用基于接口的代理,但Spring Boot根据配置属性的不同,可能会默认启用基于类的代理。
从7.0版本开始,可以通过在特定的@Bean方法或@Component类上使用@Proxyable注解来强制指定某个代理类型,@Proxyable(INTERFACES)或@Proxyable(TARGET_CLASS)会覆盖任何全局配置的默认值。对于非常特定的用途,你甚至可以通过@Proxyable(interfaces=…)来指定要使用的代理接口,从而将暴露范围限制在选定的接口上,而不仅仅是目标 bean 实现的所有接口。
理解AOP代理
Spring AOP是基于代理的。在你自己编写切面或使用Spring框架提供的任何基于Spring AOP的切面之前,理解最后这个陈述的确切含义是至关重要的。
首先考虑这样一个场景:你拥有一个普通的、未经代理的对象引用,如下代码片段所示:
- Java
- Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
如果你在一个对象引用上调用一个方法,那么该方法将直接在该对象引用上被调用,如下图和列表所示:

- Java
- Kotlin
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
fun main() {
val pojo = SimplePojo()
// this is a direct method call on the 'pojo' reference
pojo.foo()
}
当客户端代码引用的对象是一个代理时,情况会有所不同。请参考以下图表和代码片段:

- Java
- Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
这里需要理解的关键点是,在Main类的main(..)方法中的客户端代码拥有对代理的引用。这意味着对该对象引用的方法调用实际上是对代理的调用。因此,代理可以委托所有与该特定方法调用相关的拦截器(即“建议”代码)来执行。然而,一旦调用最终到达目标对象(在这个例子中是SimplePojo对象),该目标对象可能对自己进行的任何方法调用(比如this.bar()或this.foo()),都将针对this引用本身被执行,而不是通过代理。这具有重要意义:通过this引用进行的自我调用将不会让与该方法调用相关联的拦截器代码有机会被执行。换句话说,无论是显式还是隐式的this引用所导致的自我调用,都会绕过拦截器代码的执行。
为了解决这个问题,你有以下几种选择。
避免自我调用
最佳方法是(这里的“最佳”一词使用得比较宽松)对代码进行重构,以避免自我调用(self invocation)的发生。这确实需要你付出一些努力,但这是最合适、侵入性最小的方法。
注入自引用
另一种方法是利用自注入,通过自我引用而不是this来调用代理上的方法。
使用 AopContext.currentProxy()
最后这种方法是不被强烈推荐的,我们也不愿意特别指出这一点,更倾向于之前的选项。然而,作为最后的手段,你可以选择将类中的逻辑与Spring AOP关联起来,如下例所示。
- Java
- Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// This works, but it should be avoided if possible.
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// This works, but it should be avoided if possible.
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
使用 AopContext.currentProxy() 会完全将你的代码与 Spring AOP 绑定在一起,这使得类本身也意识到自己是在 AOP 环境中被使用的,这就削弱了 AOP 的一些优势。此外,还需要配置 ProxyFactory 来暴露代理对象,如下例所示:
- Java
- Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
factory.isExposeProxy = true
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
AspectJ的编译时织入(compile-time weaving)和加载时织入(load-time weaving)不存在这种自我调用问题,因为它们是在字节码内部应用切点(advice),而不是通过代理来实现的。