跳到主要内容

使用 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);
java

以下示例显示了所需的 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>
xml

前面的 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>
xml

注意目标对象(前面示例中的 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>
xml

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

演员阵容定义如下:

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

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

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

原型目标来源

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

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

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

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

ThreadLocal 目标源

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

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

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