7.10. 使用TargetSource

Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource接口进行描述。 这个接口负责返回一个实现连接点的“目标对象(target object)”。每当AOP代理处理一个方法调用时都会向TargetSource的实现请求一个目标实例。

使用Spring AOP的开发者通常不需要直接和TargetSource打交道,但这提供了一种强大的方式来支持池化(pooling),热交换(hot swappable)和其它高级目标。 例如,一个使用池来管理实例的TargetSource可以为每个调用返回一个不同的目标实例。

如果你不指定一个TargetSource,一个缺省实现将被使用,它包装一个本地对象。对于每次调用它将返回相同的目标(像你期望的那样)。

让我们看看Spring提供的标准目标源(target source)以及如何使用它们。

提示

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

7.10.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的引用的客户将不知道发生了这个修改,但是将可以立即点击新的目标。

这个例子没有添加任何通知--也不必为使用一个TargetSource添加任何通知--当然任何TargetSource都可以与任意通知联合使用。

7.10.2. 池化目标源

使用一个池化目标源提供了和无状态session EJB类似的编程模型,它维护一个包括相同实例的池,方法调用结束后将把对象释放回池中。

Spring池化和SLSB池化之间的一个决定性区别是Spring池化功能可以用于任何POJO。就像Spring通常情况下那样,这个服务是非侵入式的。

Spring对Jakarta Commons Pool 1.3提供了开箱即用的支持,后者提供了一个相当有效的池化实现。要使用这个特性,你需要在应用程序路径中存在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.CommonsPoolTargetSource">
                <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接口, 这通过一个introduction暴露配置以及当前池的大小。你需要像这样定义一个通知器:

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
                <property name="targetObject" ref="poolTargetSource"/>
                <property name="targetMethod" value="getPoolingConfigMixin"/>
            </bean>

这个通知器可以通过调用AbstractPoolingTargetSource类上的一个方便的方法来获得,因此这里使用MethodInvokingFactoryBean。 这个通知器名(这里是“poolConfigAdvisor”)必须在提供被池化对象的ProxyFactoryBean里的拦截器名列表里中。

转型看起来像这样:

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

注意

池化无状态服务对象通常是不必要的。我们不认为这(池化)应当是缺省的选择,因为多数无状态对象是先天线程安全的,如果资源被缓存,那么对实例进行池化会引起很多问题。

使用自动代理时池化更加简单。可以为任何自动代理创建器设置所使用的TargetSource

7.10.3. 原型目标源

建立一个“原型”目标源和池化TargetSource很相似。在这个例子里,当每次方法调用时,将创建一个目标的新实例。 虽然在新版本的JVM中创建一个新对象的代价并不高,但是把新对象织入(满足它的IoC依赖)可能是很昂贵的。因此如果没有很好的理由,你不应该使用这个方法。

为了做到这点,你可以把上面的poolTargetSource定义修改成下面的形式。(为了清楚说明,修改了bean的名字。)

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

这里只有一个属性:目标bean的名字。TargetSource的实现使用继承来确保命名的一致性。就像池化目标源那样,目标bean必须是一个原型的bean定义。

7.10.4. 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)和取消(unset)(后者仅仅需要调用ThreadLocal.set(null))绑定到线程的本地资源。 取消在任何情况下都应该进行,否则也许会导致错误的行为。Spring的ThreadLocal支持将为你处理这个问题,所以如果没有其它正确的处理代码,永远应该考虑使用这个功能。