6.9. 使用TargetSource实现

Spring提供了 TargetSource的概念,用org.springframework.aop.TargetSource接口表示。此接口负责返回实现连接点的“目标对象”。每次AOP代理处理方法调用时,都会要求TargetSource实现一个目标实例。
使用Spring AOP的开发人员通常不需要直接使用TargetSource实现,但这提供了支持池、热交换和其他复杂目标的强大方法。例如,池目标源可以通过使用池来管理实例,为每次调用返回不同的目标实例。
如果不指定TargetSource,则使用默认实现包装本地对象。每次调用都会返回相同的目标(如您所期望的那样)。
本节的其余部分描述了Spring提供的标准目标源以及如何使用它们。

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

6.9.1. 热交换目标源

存在org.springframework.aop.target.HotSwappable以允许AOP代理的目标切换,同时允许调用方保留对它的引用。
更改目标源的将立即生效。热交换目标源是线程安全的。
您可以使用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() 调用会更改可交换bean的目标。持有对该bean的引用的客户机不知道该更改,但立即开始触及新的目标。
尽管此示例不添加任何通知(不需要添加通知以使用TargetSource),但任何TargetSource都可以与任意通知结合使用。

6.9.2. 汇集目标源

使用池目标源为无状态会话EJB提供了类似的编程模型,其中维护了相同实例的池,方法调用将释放池中的对象。
Spring池和SLSB池的一个重要区别是Spring池可以应用于任何POJO。与一般的Spring一样,此服务可以以非侵入性的方式来应用。
Spring支持Commons Pool 2.2,它提供了一个相当有效的池实现。您需要应用程序类路径上的公共池JAR来使用此功能。您还可以将org.springframework.aop.target.AbstractPoolingTargetSource子类化,以支持任何其他池API。

还支持Commons Pool 1.5+,但从Spring Framework 4.2起已弃用。

以下列表显示了一个配置示例:

<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接口,该接口通过ntroduction公开池的配置和当前大小的信息。您需要定义类似以下内容的顾问:

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

通过对AbstractPoolingTargetSource类调用一个方便的方法来获得此Advisor,因此使用了MethodInvokingFactoryBean。此顾问的名称(此处为poolConfigAdvisor)必须位于公开池对象的ProxyFactoryBean中拦截器名称的列表中。
定义如下:

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

通常不需要合并无状态服务对象。我们不认为这应该是默认的选择,因为大多数无状态对象都是自然线程安全的,如果缓存资源,实例池也会有问题。
使用自动代理可以使用更简单的池。您可以设置任何自动代理创建者使用的TargetSource实现。

6.9.3. 原型目标源

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

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

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

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

6.9.4. 线程本地目标源

如果需要为每个传入请求(即每个线程)创建一个对象,则线程本地目标源非常有用。ThreadLocal的概念提供了一个JDK范围的工具,可以透明地将资源存储在线程旁边。设置ThreadLocalTargetSource 与其他类型的目标源的说明几乎相同,如下示例所示:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
线程本地实例在多线程和多类加载器环境中错误地使用它们时会出现严重问题(可能导致内存泄漏)。您应该始终考虑在其他类中包装一个threadlocal,而不要直接使用threadlocal本身(包装类中除外)。此外,您应该始终记住正确地设置和取消设置(后者只涉及ThreadLocal.set(null)))线程的本地资源。在任何情况下都应进行取消设置,因为不取消设置可能会导致问题行为。Spring的threadLocal支持为您提供了这一点,应该始终考虑在没有其他适当处理代码的情况下才使用threadLocal实例。

6.10. 定义新的通知类型

SpringAOP设计为可扩展的。虽然拦截实现策略目前在内部使用,但是除了围绕通知的拦截之外,还可以支持任意的通知类型,包括在前置通知、抛出后通知以及在返回后通知。
org.springframework.aop.framework.adapter 包是一个SPI包,它允许在不更改核心框架的情况下添加对新自定义通知类型的支持。对自定义通知类型的唯一约束是它必须实现org.aopalliance.aop.Advice接口。
有关更多信息,请参阅org.springframework.aop.framework.adapter javadoc。