# 5.8. 代理机制

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。JDK 动态代理内置在 JDK 中，而 CGLIB 是一个通用的开源类定义库（重新打包到`spring-core`).

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

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

* 使用 CGLIB，不能通知`final`方法，因为它们不能在运行时生成的子类中被覆盖。
* 从 Spring 4.0 开始，代理对象的构造函数不再被调用两次，因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当您的 JVM 不允许绕过构造函数时，您可能会看到来自 Spring 的 AOP 支持的双重调用和相应的调试日志条目。

要强制使用 CGLIB 代理，请将`<aop:config>`元素的`proxy-target-class`属性值设置为 true，如下所示：

```xml
<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>
```

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

```xml
<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 代理**

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

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

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

如果您在对象引用上调用方法，则直接在该对象引用上调用该方法，如下图和清单所示：

![aop 代理普通 pojo 调用](https://docs.spring.io/spring-framework/reference/_images/aop-proxy-plain-pojo-call.png)

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

当客户端代码的引用是代理时，情况会发生轻微变化。考虑下面的图表和代码片段：

![aop 代理调用](https://docs.spring.io/spring-framework/reference/_images/aop-proxy-call.png)

```java
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`类的`main(..)`方法内部的客户端代码有对代理的引用。这意味着对该对象引用的方法调用是对代理的调用。因此，代理可以委托给与该特定方法调用相关的所有拦截器（通知）。但是，一旦调用最终到达目标对象（在这种情况下为`SimplePojo`引用），它可能对自身进行的任何方法调用，例如`this.bar()`或 `this.foo()`，都将针对`this`引用而不是代理调用。这具有重要意义。这意味着自调用不会导致与方法调用相关的通知有机会运行。

好的，那该怎么办呢？最好的方法（术语“最好”在这里被松散地使用）是重构你的代码，这样自调用就不会发生。这确实需要您做一些工作，但它是最好的、侵入性最小的方法。下一种方法绝对可怕，我们不愿指出，正是因为它太可怕了。您可以（对我们来说很痛苦）将您的类中的逻辑完全绑定到 Spring AOP，如以下示例所示：

```java
public class SimplePojo implements Pojo {

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

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

这完全将您的代码与 Spring AOP 耦合在一起，并且它使类本身意识到它是在 AOP 上下文中使用的，而 AOP 上下文与 AOP 相悖。在创建代理时还需要一些额外的配置，如以下示例所示：

```java
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 框架。


---

# 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://doc.shiker.tech/spring-he-xin-gong-neng/5.-spring-mian-xiang-qie-mian-bian-cheng/5.8.-dai-li-ji-zhi.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.
