# 6.9. 使用TargetSource实现

Spring 提供了`TargetSource`的概念，在 `org.springframework.aop.TargetSource`接口中表达。该接口负责返回实现连接点的“目标对象”。`TargetSource` 每次 AOP 代理处理方法调用时，都会要求实现提供目标实例。

使用 Spring AOP 的开发人员通常不需要直接使用`TargetSource`实现，但这提供了支持池、热插拔和其他复杂目标的强大方法。例如，通过使用`TargetSource`池来管理实例，池可以为每次调用返回不同的目标实例。

如果不指定 `TargetSource`，则使用默认实现来包装本地对象。每次调用都返回相同的目标（如您所料）。

本节的其余部分描述了 Spring 提供的标准目标源以及如何使用它们。

使用自定义目标源时，您的目标通常需要是原型而不是单例 bean 定义。这允许 Spring 在需要时创建新的目标实例。

**6.9.1. 热插拔目标源**

`org.springframework.aop.target.HotSwappableTargetSource`存在让 AOP 代理的目标被切换，同时让调用者保留对它的引用。

更改目标源的目标会立即生效。`HotSwappableTargetSource`是线程安全的。

您可以使用HotSwappableTargetSource 上的`swap()`方法更改目标，如以下示例所示：

```java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
```

以下示例显示了所需的 XML 定义：

```xml
<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>
```

前面的`swap()`调用更改了可交换 bean 的目标。持有对该 bean 的引用的客户端不知道更改，但会立即开始命中新目标。

虽然这个例子没有添加任何切面（使用 `TargetSource` 不需要添加切面），但任何`TargetSource`可以与任意切面一起使用。

**6.9.2. 合并目标源**

使用池化目标源提供了与无状态会话 EJB 类似的编程模型，其中维护了相同实例的池，方法调用将释放池中的对象。

Spring pooling 和 SLSB pooling 的一个关键区别是 Spring pooling 可以应用于任何 POJO。与一般的 Spring 一样，此服务可以以非侵入方式应用。

Spring 提供对 Commons Pool 2.2 的支持，它提供了相当高效的池化实现。您需要`commons-pool`应用程序类路径中的 Jar 才能使用此功能。您还可以子类化 `org.springframework.aop.target.AbstractPoolingTargetSource`以支持任何其他池 API。

Commons Pool 1.5+ 也受支持，但自 Spring Framework 4.2 起已弃用。

以下清单显示了一个示例配置：

```xml
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>
```

请注意，目标对象（在前面的示例中的`businessObjectTarget`）必须是原型。这使`PoolingTargetSource`实现可以创建目标的新实例以根据需要增加池。有关其属性的信息，请参阅您希望使用的[`AbstractPoolingTargetSource`具体子类的javadoc 。](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/aop/target/AbstractPoolingTargetSource.html)。`maxSize`是最基本的，并且始终保证存在。

在这种情况下，需要在同一 IoC 上下文中定义拦截器`myInterceptor`的名称。但是，您无需指定拦截器即可使用池化。如果您只想要池而不需要其他切面，则根本不要设置该 `interceptorNames`属性。

您可以将 Spring 配置为能够将任何池化对象强制转换为 `org.springframework.aop.target.PoolingConfig`接口，该接口通过介绍公开有关池的配置和当前大小的信息。您需要定义类似于以下内容的Advisor：

```xml
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
```

该Advisor是通过调用 `AbstractPoolingTargetSource`类上的便利方法获得的，因此使用`MethodInvokingFactoryBean`. 此Advisor的名称（`poolConfigAdvisor`此处为 ）必须在`ProxyFactoryBean`公开池对象的拦截器名称列表中。

示例如下：

```java
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
```

通常不需要池化无状态服务对象。我们不认为它应该是默认选择，因为大多数无状态对象自然是线程安全的，如果资源被缓存，实例池是有问题的。

使用自动代理可以实现更简单的池化。您可以设置`TargetSource`任何自动代理创建者使用的实现。

**6.9.3. 原型目标源**

设置“原型”目标源类似于设置`TargetSource`池。在这种情况下，每次方法调用都会创建一个新的目标实例。尽管在现代 JVM 中创建新对象的成本并不高，但连接新对象（满足其 IoC 依赖性）的成本可能会更高。因此，如果没有充分的理由，您不应该使用这种方法。

为此，您可以修改前面显示的`poolTargetSource`定义，如下所示（为了清楚起见，我们还更改了名称）：

```xml
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
```

唯一的属性是目标 bean 的名称。在 `TargetSource`实现中使用继承来确保一致的命名。与池化目标源一样，目标 bean 必须是原型 bean 定义。

**6.9.4.`ThreadLocal`目标来源**

如果您需要为每个传入请求（即每个线程）创建一个对象，则`ThreadLocal`目标源很有用。`ThreadLocal`的概念提供了一个 JDK 范围的工具，可以透明地在线程旁边存储资源。设置 a`ThreadLocalTargetSource`与为其他类型的目标源解释的几乎相同，如以下示例所示：

```xml
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
```

`ThreadLocal`当在多线程和多类加载器环境中错误地使用它们时，实例会出现严重的问题（可能导致内存泄漏）。您应该始终考虑将 threadlocal 包装在其他类中，并且永远不要直接使用`ThreadLocal`本身（包装类除外）。此外，您应该始终记住正确设置和取消设置（后者仅涉及对 `ThreadLocal.set(null)`的调用）线程本地的资源。在任何情况下都应该取消设置，因为不取消设置可能会导致问题行为。Spring 的 `ThreadLocal`支持为您执行此操作，并且应该始终考虑支持在 `ThreadLocal`没有其他适当处理代码的情况下使用实例。
