# 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`没有其他适当处理代码的情况下使用实例。


---

# 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/6.-spring-aop-api/6.9.-shi-yong-targetsource-shi-xian.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.
