# 5.8代理机制

SpringAOP使用JDK动态代理或CGLIB为给定的目标对象创建代理。JDK动态代理内置于JDK中，而CGLIB是一个公共的开放源码类定义库（重新打包到Spring核心中）。

如果要代理的目标对象实现至少一个接口，则使用JDK动态代理。由目标类型实现的所有接口都是代理的。如果目标对象不实现任何接口，则创建一个CGLIB代理。

如果要强制使用CGLIB代理（例如，代理为目标对象定义的每个方法，而不仅仅是由其接口实现的方法），可以这样做。但是，你应该考虑以下问题：

* 对于cglib，不能通知final方法，因为它们不能在运行时生成的子类中被重写。
* 从Spring4.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 auto proxy支持时强制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代理。

## 5.8.1 理解AOP Proxies

SpringAOP是基于代理的。在编写自己的方面或使用Spring框架提供的任何基于SpringAOP的方面之前，掌握最后一条语句的实际语义是非常重要的。

首先考虑一个场景，其中你有一个普通的、未代理的、没有什么特别之处的、直接的对象引用，如下面的代码片段所示：

```
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...
    }
}
```

如果对对象引用调用方法，则直接对该对象引用调用该方法，如下图和列表所示：

![](https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/images/aop-proxy-plain-pojo-call.png)

```
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();
    }
}
```

当客户代码的引用是代理时，情况略有变化。请考虑以下图表和代码段：

![](https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/images/aop-proxy-call.png)

```
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();
    }
}
```

这里要理解的关键是，主类的main（..）方法中的代码具有对代理的引用。这意味着对该对象引用的方法调用是对代理的调用。因此，代理可以委托给与该特定方法调用相关的所有拦截器（通知）。但是，一旦调用最终到达目标对象（在本例中为SimplePojo的引用），它可以对自身进行的任何方法调用（如this.bar（）或this.foo（））都将针对该引用而不是代理进行调用。这有重要的意义。这意味着自调用不会导致与方法调用相关联的advice获得执行的机会。

好吧，那我们该怎么办？最好的方法（这里松散地使用术语“best”）是重构代码，这样就不会发生自调用。这确实需要你做一些工作，但这是最好的，微创的方法。下一个方法绝对是可怕的，我们毫不犹豫地指出它，正是因为它是如此可怕。你可以（尽管对我们来说很痛苦）将类中的逻辑完全绑定到SpringAOP，如下示例所示：

```
public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}
```

这完全将你的代码与SpringAOP结合在一起，并且它使类本身意识到它正在一个AOP上下文中使用，而这个AOP上下文在AOP的面前飞驰。在创建代理时，它还需要一些额外的配置，如下示例所示：

```
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.adddInterface(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框架。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/5spring-aop/5.8proxying-mechanisms.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
