使用TargetSource实现

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

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

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

本节的其余部分将介绍 Spring 提供的标准 target source 以及如何使用它们。

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

热插拔目标源

org.springframework.aop.target.HotSwappableTargetSource 存在是为了让 AOP 代理的目标在调用者保留对它的引用时能够被切换。

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

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

  • Java

  • Kotlin

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val 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 都可以与任意建议一起使用。

池化目标源

使用池化目标源提供了与无状态会话 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中的拦截器名称列表中。

强制转换定义如下

  • Java

  • Kotlin

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
通常不需要池化无状态服务对象。我们认为它不应该成为默认选择,因为大多数无状态对象天生就是线程安全的,如果缓存资源,实例池化会很麻烦。

使用自动代理可以实现更简单的池化。您可以设置任何自动代理创建者使用的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.set(null))线程本地资源。无论如何都应该取消设置,因为不取消设置可能会导致问题行为。Spring 的ThreadLocal支持会为您执行此操作,并且应该始终优先考虑使用ThreadLocal实例,而不是使用没有其他适当处理代码的ThreadLocal实例。