跳到主要内容
版本:6.2.3

使用 TargetSource 实现

ChatGPT-4o-mini 中英对照 Using TargetSource Implementations Using TargetSource Implementations

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

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

如果您不指定 TargetSource,则会使用默认实现来包装本地对象。每次调用都会返回相同的目标(正如您所期望的那样)。

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

提示

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

热插拔目标源​

org.springframework.aop.target.HotSwappableTargetSource 的存在是为了让 AOP 代理的目标可以被切换,同时让调用者保持对它的引用。

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

您可以通过在 HotSwappableTargetSource 上使用 swap() 方法来更改目标,如以下示例所示:

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

以下示例显示了所需的 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 都可以与任意建议一起使用。

Pooling Target Sources​

使用池化目标源提供了与无状态会话 EJB 相似的编程模型,其中维护了一组相同的实例,方法调用会发送到池中空闲的对象。

Spring 池化和 SLSB 池化之间一个关键的区别是,Spring 池化可以应用于任何 POJO。与 Spring 一般情况一样,这项服务可以以非侵入的方式应用。

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

备注

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

以下列表显示了一个示例配置:

<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 以及您希望使用的具体子类。 maxSize 是最基本的属性,并始终保证存在。

在这种情况下,myInterceptor 是一个拦截器的名称,需要在同一个 IoC 上下文中定义。然而,您不需要指定拦截器来使用池化。如果您只想要池化而不需要其他建议,请完全不设置 interceptorNames 属性。

您可以配置 Spring 以能够将任何池化对象转换为 org.springframework.aop.target.PoolingConfig 接口,该接口通过引入暴露有关配置和当前池大小的信息。您需要定义一个顾问,类似于以下内容:

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

这个顾问是通过在 AbstractPoolingTargetSource 类上调用一个便利方法获得的,因此使用了 MethodInvokingFactoryBean。这个顾问的名称(poolConfigAdvisor)必须在暴露池化对象的 ProxyFactoryBean 的拦截器名称列表中。

演员阵容定义如下:

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

池化无状态服务对象通常不是必要的。我们认为这不应该是默认选择,因为大多数无状态对象本质上是线程安全的,并且如果资源被缓存,实例池化会带来问题。

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

原型目标来源​

设置“原型”目标源与设置池化的 TargetSource 类似。在这种情况下,每次方法调用时都会创建目标的新实例。尽管在现代 JVM 中创建新对象的成本并不高,但连接新对象(满足其 IoC 依赖)的成本可能更高。因此,除非有充分的理由,否则不应使用这种方法。

要做到这一点,您可以按照以下方式修改之前显示的 poolTargetSource 定义(我们也为了清晰起见更改了名称):

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

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

ThreadLocal 目标源​

ThreadLocal 目标源在需要为每个传入请求(每个线程)创建一个对象时非常有用。ThreadLocal 的概念提供了一种 JDK 范围内的机制,可以透明地将资源存储在线程旁边。设置 ThreadLocalTargetSource 的过程与其他类型的目标源的设置过程基本相同,如以下示例所示:

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

ThreadLocal 实例在多线程和多类加载器环境中不正确使用时会带来严重问题(可能导致内存泄漏)。您应该始终考虑将 ThreadLocal 封装在其他类中,而不是直接使用 ThreadLocal 本身(除了在包装类中)。此外,您还应该始终记得正确设置和取消设置(后者涉及调用 ThreadLocal.remove())与线程相关的资源。在任何情况下都应该取消设置,因为不取消设置可能会导致问题行为。Spring 的 ThreadLocal 支持为您处理这一点,并且应该始终考虑使用它,而不是在没有其他适当处理代码的情况下使用 ThreadLocal 实例。