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,如下所示:

  1. <aop:config proxy-target-class="true">
  2. <!-- other beans defined here... -->
  3. </aop:config>

当你使用 @AspectJ 自动代理支持时,要强制 CGLIB 代理,请将 <aop:aspectj-autoproxy>元素的 proxy-target-class属性设置为true,如下所示:

  1. <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 的切面之前,你必须掌握最后一句话的语义,这一点至关重要。

首先考虑这样一种情况:你有一个普通的、无代理的、没什么特别的、直接的对象引用,正如下面的代码片段所示:

  1. public class SimplePojo implements Pojo {
  2. public void foo() {
  3. // 这个方法的调用是对 "this" 引用的直接调用。
  4. this.bar();
  5. }
  6. public void bar() {
  7. // some logic...
  8. }
  9. }

如果你在一个对象引用上调用一个方法,该方法就会直接在该对象引用上被调用,正如下面的图片和列表所示:
image.png

  1. public class Main {
  2. public static void main(String[] args) {
  3. Pojo pojo = new SimplePojo();
  4. // 这是对 "pojo" 引用的一个直接方法调用。
  5. pojo.foo();
  6. }
  7. }

当客户端代码拥有的引用是一个代理时,情况会略有变化。请看下面的图表和代码片段:
image.png

  1. public class Main {
  2. public static void main(String[] args) {
  3. ProxyFactory factory = new ProxyFactory(new SimplePojo());
  4. factory.addInterface(Pojo.class);
  5. factory.addAdvice(new RetryAdvice());
  6. Pojo pojo = (Pojo) factory.getProxy();
  7. // 这是一个对代理的方法调用!
  8. pojo.foo();
  9. }
  10. }

这里需要理解的关键是,在 Main 类的 main(.)方法里面的客户代码有一个对代理的引用。这意味着,对该对象引用的方法调用就是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(advice)。然而,一旦调用最终到达目标对象(本例中的 SimplePojo 引用),它可能对自身进行的任何方法调用,如 this.bar()this.foo()都将被调用到这个引用,而不是代理。这有很重要的意义。它意味着自我调用不会导致与方法调用相关的 advice 得到运行的机会。

好吧,那么对此该怎么做呢?最好的方法(这里的 “最好 “一词用得很宽泛)是重构你的代码,使自我调用不会发生。这确实需要你做一些工作,但这是最好的、最不具侵入性的方法。下一个方法绝对是可怕的,我们不愿意指出它,正是因为它是如此可怕。你可以(对我们来说是痛苦的)将你的类中的逻辑完全与 Spring AOP 联系起来,正如下面的例子所示:

  1. public class SimplePojo implements Pojo {
  2. public void foo() {
  3. // 这很有效,但是......嘎嘎!
  4. ((Pojo) AopContext.currentProxy()).bar();
  5. }
  6. public void bar() {
  7. // some logic...
  8. }
  9. }

这完全将你的代码与 Spring AOP 联系在一起,而且它使类本身意识到它是在 AOP 上下文中使用的,这与 AOP 背道而驰。它还需要在创建代理时进行一些额外的配置,正如下面的例子所示:

  1. public class Main {
  2. public static void main(String[] args) {
  3. ProxyFactory factory = new ProxyFactory(new SimplePojo());
  4. factory.addInterface(Pojo.class);
  5. factory.addAdvice(new RetryAdvice());
  6. factory.setExposeProxy(true);
  7. Pojo pojo = (Pojo) factory.getProxy();
  8. // this is a method call on the proxy!
  9. pojo.foo();
  10. }
  11. }

最后,必须指出的是,AspectJ 不存在这种自我调用的问题,因为它不是一个基于代理的 AOP 框架。

上面的例子其他的其实都好理解,就是那个 addAdvice 可能不知道写什么,下面是一个补充完整的例子

  1. package cn.mrcode.study.springdocsread.aspect;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. import org.aopalliance.intercept.MethodInvocation;
  4. import org.springframework.aop.framework.ProxyFactory;
  5. /**
  6. * @author mrcode
  7. */
  8. public class DemoTest {
  9. public static void main(String[] args) {
  10. ProxyFactory factory = new ProxyFactory(new SimplePojo());
  11. factory.addInterface(Pojo.class);
  12. factory.addAdvice(new MethodInterceptor() {
  13. @Override
  14. public Object invoke(MethodInvocation invocation) throws Throwable {
  15. System.out.println("代理的方法:" + invocation.getMethod());
  16. final Object proceed = invocation.proceed();
  17. System.out.println("代理的方法返回值:" + proceed);
  18. return proceed;
  19. }
  20. });
  21. Pojo pojo = (Pojo) factory.getProxy();
  22. pojo.foo();
  23. }
  24. }
  1. package cn.mrcode.study.springdocsread.aspect;
  2. /**
  3. * @author mrcode
  4. */
  5. public class SimplePojo implements Pojo {
  6. public void foo() {
  7. this.bar();
  8. }
  9. public void bar() {
  10. System.out.println(this);
  11. }
  12. }

ProxyFactory 是 spring aop 包中的一个类,用于编程方式使用 AOP 代理工厂。运行的打印信息如下

  1. 代理的方法:public abstract void cn.mrcode.study.springdocsread.aspect.Pojo.foo()
  2. cn.mrcode.study.springdocsread.aspect.SimplePojo@4671e53b
  3. 代理的方法返回值:null