Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource
接口进行描述。这个接口负责返回一个实现连接点的“目标对象(target object)”。每当AOP代理处理一个方法调用时都会向TargetSource
的实现请求一个目标实例。
使用Spring AOP的开发者通常不需要直接和TargetSource打交道,但这提供了一种强大的方式来支持池化(pooling),热交换(hot swappable)和其它高级目标。例如,一个使用池来管理实例的TargetSource可以为每个调用返回一个不同的目标实例。
如果你不指定一个TargetSource,一个缺省实现将被使用,它包装一个本地对象。对于每次调用它将返回相同的目标(像你期望的那样)。
让我们看看Spring提供的标准目标源(target source)以及如何使用它们。
提示
当使用一个自定义的目标源,你的目标通常需要是一个原型而不是一个单例的bean定义。这允许Spring在必要时创建新的目标实例。
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
都可以与任意通知联合使用。
使用一个池化目标源提供了和无状态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暴露配置以及当前池的大小。你需要像这样定义一个advisor:
<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里的拦截器名列表里中。
转型看起来像这样:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize());
注意
池化无状态服务对象通常是不必要的。我们不认为这(池化)应当是缺省的选择,因为多数无状态对象是先天线程安全的,如果资源被缓存,那么对实例进行池化会引起很多问题。
使用自动代理时池化更加简单。可以为任何自动代理创建器设置所使用的TargetSource
建立一个“原型”目标源和池化TargetSource很相似。在这个例子里,当每次方法调用时,将创建一个目标的新实例。虽然在新版本的JVM中创建一个新对象的代价并不高,但是把新对象织入(满足它的IoC依赖)可能是很昂贵的。因此如果没有很好的理由,你不应该使用这个方法。
为了做到这点,你可以把上面的poolTargetSource
定义修改成下面的形式。(为了清楚说明,修改了bean的名字。)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean>
这里只有一个属性:目标bean的名字。TargetSource的实现使用继承来确保命名的一致性。就像池化目标源那样,目标bean必须是一个原型的bean定义。
如果你需要为每个进来的请求(即每个线程)创建一个对象,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支持将为你处理这个问题,所以如果没有其它正确的处理代码,永远应该考虑使用这个功能。