使用TargetSource实现

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()方法来更改目标,如下例所示:

  • 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(无状态会话Bean)池化之间的一个关键区别是,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">
	... 省略属性
</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>

通过在AbstractPoolingTargetSource类上调用一个便利方法来获取此advisor,因此使用了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.set(null))线程本地资源。无论如何都应该取消设置,因为不取消设置可能导致问题行为。Spring的ThreadLocal支持会为您执行此操作,应始终优先考虑使用带有其他适当处理代码的ThreadLocal实例,而不是直接使用ThreadLocal实例。