使用 TargetSource
实现
Spring 提供了 TargetSource
的概念,它通过 org.springframework.aop.TargetSource
接口来表达。此接口负责返回实现连接点的“目标对象
”。每次 AOP 代理处理方法调用时,都会向 TargetSource
实现请求一个目标实例。使用 Spring AOP 的开发者通常不需要直接使用 TargetSource
实现,但这提供了一种支持池化、热插拔和其他复杂目标的强大方式。例如,一个池化的 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.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
是最基本的属性,并且始终保证存在。
在此示例中,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>
|