6.9. 使用TargetSource实现

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

使用 Spring AOP 的开发人员通常不需要直接使用TargetSource实现,但这提供了支持池、热插拔和其他复杂目标的强大方法。例如,通过使用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()调用更改了可交换 bean 的目标。持有对该 bean 的引用的客户端不知道更改,但会立即开始命中新目标。

虽然这个例子没有添加任何切面(使用 TargetSource 不需要添加切面),但任何TargetSource可以与任意切面一起使用。

6.9.2. 合并目标源

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

Spring pooling 和 SLSB pooling 的一个关键区别是 Spring pooling 可以应用于任何 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">
    ... 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是最基本的,并且始终保证存在。

在这种情况下,需要在同一 IoC 上下文中定义拦截器myInterceptor的名称。但是,您无需指定拦截器即可使用池化。如果您只想要池而不需要其他切面,则根本不要设置该 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>

该Advisor是通过调用 AbstractPoolingTargetSource类上的便利方法获得的,因此使用MethodInvokingFactoryBean. 此Advisor的名称(poolConfigAdvisor此处为 )必须在ProxyFactoryBean公开池对象的拦截器名称列表中。

示例如下:

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

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

使用自动代理可以实现更简单的池化。您可以设置TargetSource任何自动代理创建者使用的实现。

6.9.3. 原型目标源

设置“原型”目标源类似于设置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目标来源

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

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>

ThreadLocal当在多线程和多类加载器环境中错误地使用它们时,实例会出现严重的问题(可能导致内存泄漏)。您应该始终考虑将 threadlocal 包装在其他类中,并且永远不要直接使用ThreadLocal本身(包装类除外)。此外,您应该始终记住正确设置和取消设置(后者仅涉及对 ThreadLocal.set(null)的调用)线程本地的资源。在任何情况下都应该取消设置,因为不取消设置可能会导致问题行为。Spring 的 ThreadLocal支持为您执行此操作,并且应该始终考虑支持在 ThreadLocal没有其他适当处理代码的情况下使用实例。

最后更新于