|
此版本仍在开发中,尚未稳定。如需最新的稳定版本,请使用 Spring Framework 7.0.6! |
使用 TargetSource 实现
Spring 提供了 TargetSource 的概念,该概念在
org.springframework.aop.TargetSource 接口中表达。此接口负责返回实现连接点的“目标对象”。每次 AOP 代理处理方法调用时,都会向 TargetSource
实现询问目标实例。
使用Spring AOP的开发人员通常不需要直接与TargetSource实现打交道,但这提供了一种强大的手段来支持池化、热插拔和其他复杂的目标。例如,一个池化的TargetSource可以在每次调用时返回不同的目标实例,通过使用池来管理实例。
如果您没有指定 TargetSource,将使用默认实现来包装本地对象。每次调用都会返回相同的target(正如您所期望的)。
本节的其余部分描述了Spring提供的标准目标源以及如何使用它们。
| 使用自定义目标源时,你的目标通常需要是一个原型而不是单例 bean 定义。这允许 Spring 在需要时创建一个新的目标实例。 |
热交换目标源
The 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 提供了支持,它提供了一个相当高效的池化实现。要使用此功能,您需要将 commons-pool2-0.x.x.jar 添加到应用程序的类路径中。您还可以继承 GenericObjectPoolConfig 以支持任何其他池化API。
以下列表展示了一个示例配置:
<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>
这个advisor是通过调用AbstractPoolingTargetSource类上的一个便捷方法获得的,因此使用了MethodInvokingFactoryBean。这个advisor的名称(这里的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.remove())线程本地资源。无论何种情况都应执行清除操作,因为不清除可能会导致异常行为。Spring 的 ThreadLocal 支持功能可为你自动完成这些操作,在使用 ThreadLocal 实例时,应优先考虑使用该功能,而非缺乏适当处理代码的直接使用方式。 |