6.9使用TargetSource的实现

6.9 使用TargetSource的实现

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

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

如果不指定TargetSource,则使用默认实现包装本地对象。每次调用都会返回相同的目标(如你所期望的那样)。

本节的其余部分描述了Spring提供的标准目标源以及如何使用它们。

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

6.9.1 热交换目标源

存在org.springframework.aop.target.HotSwappableTargetSource以允许AOP代理的目标切换,同时允许调用方保留对它的引用。

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

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

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object 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()的执行改变了swappable bean的目标。持有对该bean的引用的客户端不知道该更改,并且立即开始使用新的目标。

尽管此示例不添加任何advice(不需要添加advice以使用TargetSource),但任何TargetSource都可以与任意advice结合使用。

6.9.2 Pooling Target Sources

使用pooling target source为无状态会话EJB提供了类似的编程模型,其中维护了相同实例的池,方法调用将释放池中的对象。

Spring池和SLSB池的一个重要区别是Spring池可以应用于任何POJO。与一般的Spring一样,此服务可以以非侵入性的方式应用。

Spring支持Commons Pool 2.2,它提供了一个相当有效的池实现。你需要应用程序类路径上的commons-pool来使用此功能。你还可以将org.springframework.aop.target.AbstractPoolingTargetSource子类化,以支持任何其他池API。

在Spring Framework 4.2中,Commons Pool 1.5+已不被推荐使用。

下面是一个配置的例子:

<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类调用一个方便的方法来获得此Advisor,因此使用了MethodInvokingFactoryBean。此顾问的名称(此处为PoolConfigdVisor)必须位于公开池对象的ProxyFactoryBean中拦截器名称的列表中。

其定义如下:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

通常不需要Pooling无状态服务对象。我们不认为这应该是默认的选择,因为大多数无状态对象都是自然线程安全的,如果缓存资源,实例池也会有问题。

使用自动代理可以使池的使用更加简单。TargetSource的实现可以被用在任何auto-proxy的创建者。

6.9.3 Prototype Target Sources

设置“prototype”目标源类似于设置池TargetSource。在这种情况下,每次方法调用都会创建目标的新实例。虽然在现代的JVM中创建新对象的成本并不高,但是连接新对象(满足其IOC依赖性)的成本可能更高。因此,如果没有很好的理由,你不应该使用这种方法。

为此,你可以修改前面显示的poolTargetSource定义,如下所示(为了清晰起见,我们还更改了名称):

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

唯一的属性是目标bean的名称。在TargetSource实现中使用继承以确保一致的命名。与池目标源一样,目标bean必须是原型bean定义。

6.9.4 ThreadLocal Target Sources

如果需要为每个传入请求(即每个线程)创建一个对象,则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实例。