# 6.4使用ProxyFactoryBean来创建AOP代理

如果将SpringIOC容器（applicationContext或beanFactory）用于业务对象，你希望使用Spring的AOP FactoryBean实现之一。（请记住，工厂bean引入了一个间接层，允许它创建不同类型的对象。）

> SpringAOP支持还使用了工厂级的bean。

在Spring中创建AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。这可以完全控制切入点、任何适用的advice以及它们的顺序。但是，如果你不需要这样的控制，可以选择更简单的选项。

## 6.4.1 Basics

ProxyFactoryBean与其他Spring FactoryBean实现一样，引入了一个间接级别。如果定义一个名为foo的ProxyFactoryBean，则引用foo的对象不会看到ProxyFactoryBean实例本身，而是由ProxyFactoryBean中getobject（）方法的实现创建的对象。此方法创建包装目标对象的AOP代理。

使用ProxyFactoryBean或其他具有IOC意识的类来创建AOP代理最重要的好处之一是，通知和切入点也可以由IOC管理。这是一个强大的特性，使得某些方法很难用其他AOP框架实现。例如，一个建议本身可以引用应用程序对象（除了目标之外，目标应该在任何AOP框架中都可用），这得益于依赖注入提供的所有可插入性。

## 6.4.2 JavaBean属性

与Spring提供的大多数FactoryBean实现相同，proxyFactoryBean类本身就是一个JavaBean。其属性用于：

* 指定要代理的目标。
* 指定是否使用cglib（稍后介绍，另请参阅基于jdk和cglib的代理）。

一些关键属性继承自org.spring framework.aop.framework.ProxyConfig（Spring中所有AOP代理工厂的超类）。这些关键属性包括：

* proxyTargetClass：如果要代理目标类，而不是目标类的接口，则为true。如果将此属性值设置为true，则会创建CGLIB代理（也可参见基于JDK和CGLIB的代理）。
* 优化：控制是否对通过cglib创建的代理应用积极的优化。除非你完全理解相关AOP代理如何处理优化，否则不应随意使用此设置。这目前仅用于cglib代理。它对JDK动态代理没有影响。
* 冻结：如果代理配置被冻结，则不再允许更改配置。这对于轻微的优化和不希望调用方能够在创建代理后（通过建议的接口）操纵代理的情况都很有用。此属性的默认值为false，因此允许进行更改（例如添加其他通知）。
* ExposeProxy：确定当前代理是否应在ThreadLocal中公开，以便目标可以访问它。如果目标需要获取代理，并且ExposeProxy属性设置为true，则该目标可以使用AopContext.currentProxy（）方法。

ProxyFactoryBean特有的其他属性包括：

* proxyInterfaces：字符串接口名称的数组。如果没有提供，将使用目标类的cglib代理（也可参见基于jdk和cglib的代理）。
* 拦截器：要应用的顾问、拦截器或其他建议名称的字符串数组。Ordering非常重要，以先到先得的方式提供。也就是说，列表中的第一个拦截器是第一个能够拦截调用的拦截器。

名称是当前工厂中的bean名称，包括来自祖先工厂的bean名称。这里不能提到bean引用，因为这样做会导致ProxyFactoryBean忽略通知的singleton设置。

你可以附加一个带有星号（\*）的拦截器名称。这样做会导致所有AdvisorBean的应用，其名称以要应用的星号前面的部分开头。你可以在使用“全局”Advisor中找到使用此功能的示例。

singleton：工厂是否应该返回单个对象，无论调用getObject（）方法的频率如何。一些FactoryBean实现提供了这样的方法。默认值为true。如果你想使用有状态的建议（例如，对于有状态的混合），可以使用原型建议和单例值false。

## 6.4.3 JDK- and CGLIB-based代理

本节是关于ProxyFactoryBean如何选择为特定目标对象（要代理）创建基于JDK的代理或基于CGLIB的代理的最终文档。

> ProxyFactoryBean在创建基于jdk或cglib的代理方面的行为在Spring的1.2.x和2.0版本之间发生了变化。在自动检测接口方面，ProxyFactoryBean现在显示出与TransactionProxyFactoryBean类相似的语义。

如果要代理的目标对象的类（以下简称为目标类）不实现任何接口，则创建基于CGLIB的代理。这是最简单的场景，因为JDK代理是基于接口的，没有接口意味着JDK代理甚至不可能实现。你可以插入目标bean并通过设置interceptorNames属性指定拦截器列表。注意，即使ProxyFactoryBean的proxyTargetClass属性设置为false，也会创建基于cglib的代理。（这样做毫无意义，最好从bean定义中删除，因为它充其量是多余的，最坏的情况是令人困惑。）

如果目标类实现一个（或多个）接口，则创建的代理的类型取决于proxyFactoryBean的配置。

如果proxyFactoryBean的proxyTargetClass属性设置为true，则会创建基于cglib的代理。这是有道理的，符合最不令人惊讶的原则。即使proxyFactoryBean的proxyInterfaces属性已设置为一个或多个完全限定的接口名称，proxyTargetClass属性设置为true的事实也会导致基于cglib的代理生效。

如果proxyFactoryBean的proxyInterfaces属性已设置为一个或多个完全限定的接口名称，则将创建基于JDK的代理。创建的代理实现在proxyInterfaces属性中指定的所有接口。如果目标类恰好实现了比proxyInterfaces属性中指定的接口多得多的接口，那么这一切都很好，但是这些附加接口不是由返回的代理实现的。

如果尚未设置proxyfactorybean的proxyInterfaces属性，但目标类确实实现了一个（或多个）接口，则proxyfactorybean会自动检测到目标类确实实现了至少一个接口，并创建了基于JDK的代理。实际代理的接口是目标类实现的所有接口。实际上，这与向proxyInterfaces属性提供目标类实现的每个接口的列表相同。然而，它的工作量明显减少，而且不容易出现排版错误。

## 6.4.4 代理接口

考虑一个简单的ProxyFactoryBean实例。这个例子涉及：

* 代理的目标bean。这是示例中的PersonTarget bean定义。
* 用来提供建议的Advisor和Interceptor。
* AOP代理bean定义，用于指定目标对象（PersonTarget bean）

下面的列表显示了示例：

```markup
<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>
```

注意，interceptorNames属性采用一个字符串列表，其中包含当前工厂中拦截器或顾问的bean名称。你可以在返回之前、之后使用顾问、拦截器，并抛出advice对象。advisors的ordering意义重大。

> 你可能想知道为什么列表不包含bean引用。原因是，如果proxyfactorybean的singleton属性设置为false，它必须能够返回独立的代理实例。如果任何顾问本身是原型，则需要返回一个独立的实例，因此需要能够从工厂获取原型的实例。持有reference是不够的。

前面显示的PersonBean定义可以用来代替Person实现，如下所示：

```java
Person person = (Person) factory.getBean("person");
```

同一个IOC上下文中的其他bean可以用普通Java对象表示它的强类型依赖性。以下示例显示了如何执行此操作：

```markup
<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>
```

此示例中的PersonUser类暴露了一个Person类型的属性。就其而言，可以透明地使用AOP代理来代替“真实的”人员实现。但是，它的类将是动态代理类。可以将其转换为Advised接口（稍后讨论）。

你可以使用匿名内部bean隐藏目标和代理之间的区别。只有ProxyFactoryBean定义不同。使用该advice仅为了完整性。下面的示例演示如何使用匿名内部bean：

```markup
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>
```

使用匿名内部bean的优点是只有一个person类型的对象。如果我们希望防止应用程序上下文的用户获得对un-advised对象的引用，或者需要避免SpringIOC autowiring的任何模糊性，那么这是非常有用的。也可以说，proxyfactorybean定义是独立的，这是一个优势。但是，有时能够从工厂获得un-advised的目标实际上是一种优势（例如，在某些测试场景中）。

## 6.4.5 代理Classes

如果你需要代理一个类，而不是一个或多个接口呢？

假设在前面的示例中，没有Person界面。我们需要通知一个名为person的类，该类没有实现任何业务接口。在这种情况下，可以将spring配置为使用cglib代理，而不是动态代理。为此，请将前面显示的proxyFactoryBean上的proxyTargetClass属性设置为true。虽然最好是编程到接口而不是类，但是在处理遗留代码时，advise不实现接口的类的能力是非常有用的。（一般来说，Spring不是规定性的。虽然它使应用好的实践变得容易，但它避免了强制使用特定的方法。）

如果你愿意，你可以在任何情况下强制使用cglib，即使你确实有接口。

CGLIB代理通过在运行时生成目标类的子类来工作。Spring将生成的子类配置为将方法调用委托给原始目标。子类用于实现装饰器模式，编织在advice中。

cglib代理通常对用户是透明的。但是，有一些问题需要考虑：

* 无法通知Final方法，因为它们不能被重写。
* 不需要将cglib添加到类路径中。从Spring3.2开始，cglib被重新包装并包含在SpringCoreJar中。换句话说，基于cglib的AOP和JDK动态代理一样“开箱即用”。

CGLIB代理和动态代理的性能差别不大。在这种情况下，性能不应是决定性的考虑因素。

## 6.4.6 使用全局的Advisors

通过向拦截器名称添加星号，所有具有与星号之前部分匹配的bean名称的advisor都将添加到advisor链中。如果你需要添加一组标准的“全局”Advisor，这将非常有用。以下示例定义了两个全局Advisors：

```markup
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
```


---

# 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/6.spring-aop-apis/6.4proxyfactorybean-aop.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.
