|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
使用 TargetSource 实现
Spring 提供了 TargetSource 的概念,该概念由 org.springframework.aop.TargetSource 接口表示。此接口负责返回实现连接点(join point)的“目标对象”。每当 AOP 代理处理方法调用时,都会向 TargetSource 实现请求一个目标实例。
使用 Spring AOP 的开发者通常不需要直接操作 TargetSource 的实现,但它提供了一种强大的机制,用于支持对象池、热替换以及其他复杂的代理目标。例如,一个基于对象池的 TargetSource 可以通过池来管理实例,从而在每次方法调用时返回不同的目标实例。
如果你没有指定 TargetSource,则会使用一个默认实现来包装本地对象。每次调用都会返回同一个目标对象(正如你所期望的那样)。
本节其余部分将介绍 Spring 提供的标准目标源(target sources)以及如何使用它们。
| 使用自定义目标源时,您的目标通常需要是原型(prototype)bean 定义,而不是单例(singleton)bean 定义。这使得 Spring 能够在需要时创建一个新的目标实例。 |
可热替换的目标源
org.springframework.aop.target.HotSwappableTargetSource 的存在是为了允许在调用者保持对其引用的同时,切换 AOP 代理的目标对象。
更改目标源(target source)的目标会立即生效。HotSwappableTargetSource 是线程安全的。
你可以通过在 HotSwappableTargetSource 上使用 swap() 方法来更改目标,如下例所示:
-
Java
-
Kotlin
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val 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 都可以与任意通知结合使用。
池化目标源
使用池化目标源(pooling target source)提供了一种与无状态会话EJB类似的编程模型,其中维护一个由相同实例组成的池,方法调用会被分派给池中空闲的对象。
Spring 池化与 SLSB(无状态会话 Bean)池化之间的一个关键区别在于,Spring 池化可以应用于任何 POJO。与 Spring 框架整体一样,该服务可以以一种非侵入性的方式应用。
Spring 提供了对 Commons Pool 2.2 的支持,该库提供了一种相当高效的池化实现。要在应用程序中使用此功能,您需要在类路径(classpath)中包含 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)必须是原型(prototype)。这使得PoolingTargetSource实现能够根据需要创建新的目标实例以扩展池。有关其属性的信息,请参阅AbstractPoolingTargetSource的 Javadoc以及您希望使用的具体子类。maxSize是最基本的实现,并且始终保证存在。
在这种情况下,myInterceptor 是一个拦截器的名称,该拦截器需要在同一个 IoC 容器中定义。然而,使用池化功能时并不一定需要指定拦截器。如果你只需要池化而不需要其他通知(advice),则完全不必设置 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>
该通知器是通过调用 AbstractPoolingTargetSource 类上的一个便捷方法获得的,因此使用了 MethodInvokingFactoryBean。此通知器的名称(此处为 poolConfigAdvisor)必须包含在暴露池化对象的 ProxyFactoryBean 的拦截器名称列表中。
转换定义如下:
-
Java
-
Kotlin
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
| 通常没有必要对无状态的服务对象进行池化。我们认为这不应当作为默认选择,因为大多数无状态对象本身是线程安全的,而且如果对象缓存了资源,实例池化反而会带来问题。 |
通过使用自动代理(auto-proxying)可以实现更简单的池化。您可以设置任何自动代理创建器所使用的 TargetSource 实现。
原型目标源
设置一个“原型”目标源(prototype target source)与设置池化(pooling)的TargetSource类似。在这种情况下,每次方法调用时都会创建一个新的目标实例。尽管在现代JVM中创建新对象的成本并不高,但装配新对象(满足其IoC依赖)的成本可能更高。因此,除非有非常充分的理由,否则你不应使用这种方法。
为此,您可以按如下方式修改前面所示的 poolTargetSource 定义
(为了清晰起见,我们也更改了名称):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 bean 的名称。在 TargetSource 的实现中使用了继承,以确保命名的一致性。与池化目标源(pooling target source)一样,目标 bean 必须是一个原型(prototype)bean 定义。
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(null))线程本地资源。无论何种情况都必须执行清除操作,因为如果不进行清除,可能会导致异常行为。Spring 对 ThreadLocal 的支持会自动为您处理这些操作,因此应优先考虑使用 Spring 的支持,而不是在没有其他适当处理代码的情况下直接使用 ThreadLocal 实例。 |