使用 TargetSource 实现
TargetSource Implementations
Spring提供了TargetSource的概念,该概念在org.springframework.aop.TargetSource接口中有所体现。这个接口负责返回实现连接点(join point)的“目标对象”。每次AOP代理处理方法调用时,都会要求TargetSource实现提供一个目标实例(target instance)。
使用Spring AOP的开发者通常不需要直接处理TargetSource的实现,但这提供了一种强大的方式来支持资源池、热插拔以及其他复杂的目标管理功能。例如,一个资源池化的TargetSource可以通过使用池来管理实例,从而在每次调用时返回不同的目标实例。
如果你没有指定TargetSource,则会使用一个默认实现来包装一个本地对象。每次调用都会返回相同的目标(正如你所期望的那样)。
本节的其余部分将描述Spring提供的标准目标源代码,以及如何使用它们。
当使用自定义目标源时,您的目标通常需要是一个原型(prototype),而不是单例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,后者提供了一种相当高效的池化实现。要使用此功能,你需要在应用程序的类路径中包含 commons-pool JAR 文件。你也可以继承 org.springframework.aop.target.AbstractPoolingTargetSource 类来支持其他任何池化 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接口,该接口通过引用来暴露有关配置和池当前大小的信息。你需要定义一个类似于以下的顾问(advisor):
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
此顾问(advisor)是通过调用AbstractPoolingTargetSource类上的一个便捷方法(convenience method)来获得的,因此使用了MethodInvokingFactoryBean。这个顾问的名称(此处为poolConfigAdvisor)必须存在于暴露池化对象(pooled object)的ProxyFactoryBean中的拦截器名称列表(list of interceptors)中。
演员阵容定义如下:
- 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)
通常没有必要池化无状态服务对象。我们不认为这应该是默认选择,因为大多数无状态对象本质上是线程安全的,而如果资源被缓存的话,实例池化就会带来问题。
通过使用自动代理(auto-proxying),可以更简单地实现资源池化(pooling)。你可以设置任何自动代理创建器(auto-proxy creator)所使用的TargetSource实现。
原型目标源
设置一个“原型”目标源(prototype target source)与设置一个池化目标源(pooling TargetSource)类似。在这种情况下,每次方法调用都会创建一个新的目标对象实例。尽管在现代Java虚拟机(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实例时,应优先考虑Spring的默认处理方式,而无需额外的自定义处理代码。