对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

使用 TargetSource 实现

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

使用Spring AOP的开发人员通常不需要直接与TargetSource实现打交道,但这提供了一种强大的手段来支持池化、热插拔和其他复杂的目标。例如,一个池化的TargetSource可以在每次调用时返回不同的目标实例,通过使用池来管理实例。spring-doc.cadn.net.cn

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

本节的其余部分描述了Spring提供的标准目标源以及如何使用它们。spring-doc.cadn.net.cn

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

热交换目标源

The org.springframework.aop.target.HotSwappableTargetSource 存在是为了在让调用者保留对它的引用的同时,允许切换AOP代理的目标。spring-doc.cadn.net.cn

更改目标源的目标会立即生效。HotSwappableTargetSource 是线程安全的。spring-doc.cadn.net.cn

您可以通过使用 HotSwappableTargetSource 上的 swap() 方法来更改目标,如下例所示:spring-doc.cadn.net.cn

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

以下示例展示了所需的XML定义:spring-doc.cadn.net.cn

<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 引用的客户端不会意识到更改,但会立即开始调用新的目标。spring-doc.cadn.net.cn

虽然这个示例没有添加任何通知(使用TargetSource并不需要添加通知),但任何TargetSource都可以与任意通知结合使用。spring-doc.cadn.net.cn

池化目标源

使用池化目标源提供了一个类似于无状态会话 EJB 的编程模型,在这种模型中,维护了一组相同的实例池,方法调用会发送到池中的空闲对象。spring-doc.cadn.net.cn

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

Spring 提供了对 Commons Pool 2.2 的支持,它提供了一个相当高效的池实现。你需要将 commons-pool Jar 添加到应用程序的类路径中才能使用此功能。你还可以通过继承 org.springframework.aop.target.AbstractPoolingTargetSource 来支持任何其他池 API。spring-doc.cadn.net.cn

Commons Pool 1.5+ 也受到支持,但自 Spring 框架 4.2 起已废弃。

以下列表展示了一个示例配置:spring-doc.cadn.net.cn

<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是最基本的,并且始终保证存在。spring-doc.cadn.net.cn

在这种情况下,myInterceptor 是拦截器的名称,该拦截器需要在同一个IoC上下文中定义。但是,您不需要指定拦截器来使用池化。如果您只需要池化而不使用其他通知,则根本不需要设置 interceptorNames 属性。spring-doc.cadn.net.cn

您可以配置Spring,使其能够将任何池对象转换为org.springframework.aop.target.PoolingConfig接口,该接口通过引入提供了有关池的配置和当前大小的信息。您需要定义一个类似于以下的顾问:spring-doc.cadn.net.cn

<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中拦截器名称列表中。spring-doc.cadn.net.cn

定义的类型转换如下:spring-doc.cadn.net.cn

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实现。spring-doc.cadn.net.cn

原型目标源

设置一个“原型”目标源类似于设置一个池化TargetSource。在这种情况下,每次方法调用时都会创建目标的新实例。尽管在现代JVM中创建新对象的成本不高,但满足新对象的IoC依赖关系的成本可能会更高。因此,除非有充分的理由,否则不应使用这种方法。spring-doc.cadn.net.cn

要实现这一点,你可以按照以下方式修改前面展示的 poolTargetSource 定义 (为了清晰起见,我们也更改了名称):spring-doc.cadn.net.cn

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

唯一属性是目标bean的名称。在TargetSource实现中使用继承以确保命名一致。与池化目标源类似,目标bean必须是一个原型bean定义。spring-doc.cadn.net.cn

ThreadLocal 目标源文件

ThreadLocal 目标源在需要为每个传入请求(即每个线程)创建一个对象时非常有用。ThreadLocal 的概念提供了在整个JDK中透明地将资源与线程一起存储的设施。设置 ThreadLocalTargetSource 基本上与前面解释的其他类型的目标源相同,如下例所示:spring-doc.cadn.net.cn

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
	<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal个实例在多线程和多类加载器环境中使用不当时会引发严重问题(可能导致内存泄漏)。您必须始终考虑将ThreadLocal封装在其他类中,切勿直接使用ThreadLocal本身(封装类除外)。同时,务必正确设置线程本地资源,并在使用后取消设置(后者只需调用ThreadLocal.set(null))。任何情况下都应执行取消设置操作,未取消设置可能导致异常行为。Spring的ThreadLocal支持机制已为您处理此问题,因此应优先采用此方案,而非直接使用未经妥善处理的ThreadLocal实例。