使用 TargetSource 实现
Spring 提供了 TargetSource 的概念,它通过 org.springframework.aop.TargetSource 接口表示。此接口负责返回实现连接点的“目标对象”。每次 AOP 代理处理方法调用时,都会向 TargetSource 实现请求一个目标实例。
使用 Spring AOP 的开发者通常不需要直接使用 TargetSource 实现,但这提供了一种强大的方式来支持池化、热插拔和其他复杂的 targets。例如,一个池化 TargetSource 可以通过使用一个池来管理实例,为每次调用返回一个不同的目标实例。
如果您未指定 TargetSource,则使用默认实现来包装本地对象。每次调用都会返回同一个目标(如您所期望的)。
本节的其余部分描述了 Spring 提供的标准目标源以及如何使用它们。
| 使用自定义目标源时,您的目标通常需要是原型而非单例 bean 定义。这允许 Spring 在需要时创建新的目标实例。 |
热插拔目标源
org.springframework.aop.target.HotSwappableTargetSource 允许在调用者保持对其引用的同时切换 AOP 代理的目标。
更改目标源的目标会立即生效。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 都可以与任意通知结合使用。
池化目标源
使用池化目标源提供了一种类似于无状态会话 EJB 的编程模型,其中维护一个相同的实例池,方法调用会发送到池中空闲的对象。
Spring 池化与 SLSB 池化的一个关键区别在于,Spring 池化可以应用于任何 POJO。与 Spring 通常情况一样,此服务可以以非侵入式方式应用。
Spring 为 Commons Pool 2 提供了支持,它提供了一个相当高效的池化实现。您需要将 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.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 接口,该接口通过引入公开有关池配置和当前大小的信息。您需要定义一个类似于以下的切面
<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)
| 池化无状态服务对象通常不是必需的。我们不认为它应该是默认选择,因为大多数无状态对象天生就是线程安全的,并且如果资源被缓存,实例池化会存在问题。 |
通过使用自动代理可以实现更简单的池化。您可以设置任何自动代理创建器使用的 TargetSource 实现。
原型目标源
设置“原型”目标源类似于设置池化 TargetSource。在这种情况下,每次方法调用都会创建一个新的目标实例。虽然在现代 JVM 中创建新对象的成本不高,但连接新对象(满足其 IoC 依赖项)的成本可能更高。因此,如果没有非常充分的理由,您不应使用此方法。
为此,您可以按如下方式修改前面显示的 poolTargetSource 定义(为清晰起见,我们还更改了名称):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 bean 的名称。TargetSource 实现中使用了继承来确保命名的一致性。与池化目标源一样,目标 bean 必须是原型 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.remove())线程本地的资源。无论如何都应该取消设置,因为不取消设置可能会导致问题行为。Spring 的 ThreadLocal 支持为您完成了这项工作,并且应该始终优先于不带其他适当处理代码的 ThreadLocal 实例。 |