Spring AOP 使用 JDK 动态代理或 CGLIB 来为特定的目标对象创建代理。JDK 动态代理是内置于 JDK 中的,而 CGLIB 是一个普通的开源类定义库(重新打包到 spring-core 中)。
如果要代理的 目标对象至少实现了一个接口,就会使用 JDK 动态代理。目标类型实现的所有接口都被代理了。如果目标对象没有实现任何接口,就会创建一个 CGLIB 代理。
如果你想强制使用 CGLIB 代理(例如,代理为目标对象定义的每个方法,而不仅仅是由其接口实现的方法),你可以这样做。然而,你应该考虑以下问题。
- 在 CGLIB 中,
final
方法不能被 advice,因为它们不能在运行时生成的子类中被重写。 - 从 Spring 4.0 开始,代理对象的构造函数不再被调用两次,因为 CGLIB 代理实例是通过 Objenesis 创建的。只有在你的 JVM 不允许绕过构造器的情况下,你才可能看到双重调用和 Spring 的 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"/>
:::info
多个 <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 代理。
:::
了解 AOP 代理
Spring AOP 是基于代理的。在你编写自己的切面或使用 Spring 框架提供的任何基于 Spring AOP 的切面之前,你必须掌握最后一句话的语义,这一点至关重要。
首先考虑这样一种情况:你有一个普通的、无代理的、没什么特别的、直接的对象引用,正如下面的代码片段所示:
public class SimplePojo implements Pojo {
public void foo() {
// 这个方法的调用是对 "this" 引用的直接调用。
this.bar();
}
public void bar() {
// some logic...
}
}
如果你在一个对象引用上调用一个方法,该方法就会直接在该对象引用上被调用,正如下面的图片和列表所示:
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// 这是对 "pojo" 引用的一个直接方法调用。
pojo.foo();
}
}
当客户端代码拥有的引用是一个代理时,情况会略有变化。请看下面的图表和代码片段:
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();
// 这是一个对代理的方法调用!
pojo.foo();
}
}
这里需要理解的关键是,在 Main 类的 main(.)
方法里面的客户代码有一个对代理的引用。这意味着,对该对象引用的方法调用就是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(advice)。然而,一旦调用最终到达目标对象(本例中的 SimplePojo 引用),它可能对自身进行的任何方法调用,如 this.bar()
或 this.foo()
,都将被调用到这个引用,而不是代理。这有很重要的意义。它意味着自我调用不会导致与方法调用相关的 advice 得到运行的机会。
好吧,那么对此该怎么做呢?最好的方法(这里的 “最好 “一词用得很宽泛)是重构你的代码,使自我调用不会发生。这确实需要你做一些工作,但这是最好的、最不具侵入性的方法。下一个方法绝对是可怕的,我们不愿意指出它,正是因为它是如此可怕。你可以(对我们来说是痛苦的)将你的类中的逻辑完全与 Spring AOP 联系起来,正如下面的例子所示:
public class SimplePojo implements Pojo {
public void foo() {
// 这很有效,但是......嘎嘎!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
这完全将你的代码与 Spring AOP 联系在一起,而且它使类本身意识到它是在 AOP 上下文中使用的,这与 AOP 背道而驰。它还需要在创建代理时进行一些额外的配置,正如下面的例子所示:
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();
}
}
最后,必须指出的是,AspectJ 不存在这种自我调用的问题,因为它不是一个基于代理的 AOP 框架。
上面的例子其他的其实都好理解,就是那个 addAdvice 可能不知道写什么,下面是一个补充完整的例子
package cn.mrcode.study.springdocsread.aspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
/**
* @author mrcode
*/
public class DemoTest {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("代理的方法:" + invocation.getMethod());
final Object proceed = invocation.proceed();
System.out.println("代理的方法返回值:" + proceed);
return proceed;
}
});
Pojo pojo = (Pojo) factory.getProxy();
pojo.foo();
}
}
package cn.mrcode.study.springdocsread.aspect;
/**
* @author mrcode
*/
public class SimplePojo implements Pojo {
public void foo() {
this.bar();
}
public void bar() {
System.out.println(this);
}
}
ProxyFactory 是 spring aop 包中的一个类,用于编程方式使用 AOP 代理工厂。运行的打印信息如下
代理的方法:public abstract void cn.mrcode.study.springdocsread.aspect.Pojo.foo()
cn.mrcode.study.springdocsread.aspect.SimplePojo@4671e53b
代理的方法返回值:null