Using TargetSource Implementations

  • 热插拔目标源:允许在不中断代理的情况下更换目标。

  • 池化目标源:维护一组相同实例的池,方法调用将转到池中的空闲对象。

  • 原型目标源:每次方法调用都会创建目标的新实例。

  • ThreadLocal 目标源:为每个传入请求创建目标对象。

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

Spring offers the concept of a TargetSource, expressed in the org.springframework.aop.TargetSource interface. This interface is responsible for returning the “target object” that implements the join point. The TargetSource implementation is asked for a target instance each time the AOP proxy handles a method invocation.

使用 Spring AOP 的开发人员通常无需直接处理 TargetSource 实现,但它提供了一种支持池、热插拔和其他复杂目标的功能强大的方法。例如,池 TargetSource 可以通过使用池来管理实例,为每次调用返回一个不同的目标实例。

Developers who use Spring AOP do not normally need to work directly with TargetSource implementations, but this provides a powerful means of supporting pooling, hot swappable, and other sophisticated targets. For example, a pooling TargetSource can return a different target instance for each invocation, by using a pool to manage instances.

如果你未指定 TargetSource,则会使用默认实现来包装本地对象。每次调用都会返回相同目标(正如你所期望的那样)。

If you do not specify a TargetSource, a default implementation is used to wrap a local object. The same target is returned for each invocation (as you would expect).

本节的其余部分介绍了 Spring 提供的标准目标源,以及如何使用它们。

The rest of this section describes the standard target sources provided with Spring and how you can use them.

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

When using a custom target source, your target will usually need to be a prototype rather than a singleton bean definition. This allows Spring to create a new target instance when required.

Hot-swappable Target Sources

org.springframework.aop.target.HotSwappableTargetSource 存在,以便在允许调用者保留对其引用的同时,让 AOP 代理的目标可以切换。

The org.springframework.aop.target.HotSwappableTargetSource exists to let the target of an AOP proxy be switched while letting callers keep their references to it.

更改目标源的目标会立即生效。HotSwappableTargetSource 是线程安全的。

Changing the target source’s target takes effect immediately. The HotSwappableTargetSource is thread-safe.

你可以使用 HotSwappableTargetSource 上的 swap() 方法更改目标,如下例所示:

You can change the target by using the swap() method on HotSwappableTargetSource, as the follow example shows:

  • 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 定义:

The following example shows the required XML definitions:

<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 引用客户端不知道此更改,但会立即开始命中新目标。

The preceding swap() call changes the target of the swappable bean. Clients that hold a reference to that bean are unaware of the change but immediately start hitting the new target.

尽管此示例未添加任何通知(向 TargetSource 添加通知不是必需的),但任何 TargetSource 都可以与任意通知一起使用。

Although this example does not add any advice (it is not necessary to add advice to use a TargetSource), any TargetSource can be used in conjunction with arbitrary advice.

Pooling Target Sources

使用池目标源提供与无状态会话 EJB 类似的编程模型,其中维护一组相同的实例,方法调用将转到池中的空闲对象。

Using a pooling target source provides a similar programming model to stateless session EJBs, in which a pool of identical instances is maintained, with method invocations going to free objects in the pool.

Spring 池和 SLSB 池之间的关键区别在于,Spring 池可应用到任何 POJO。与 Spring 一样,此服务可以以非侵入方式应用。

A crucial difference between Spring pooling and SLSB pooling is that Spring pooling can be applied to any POJO. As with Spring in general, this service can be applied in a non-invasive way.

Spring 提供了对 Commons Pool 2.2 的支持,它提供了一个相当有效的池实现。你需要在应用程序的类路径上使用 commons-pool Jar,才能使用此功能。你还可以子类化 org.springframework.aop.target.AbstractPoolingTargetSource,以支持任何其他池 API。

Spring provides support for Commons Pool 2.2, which provides a fairly efficient pooling implementation. You need the commons-pool Jar on your application’s classpath to use this feature. You can also subclass org.springframework.aop.target.AbstractPoolingTargetSource to support any other pooling API.

同样支持 Commons Pool 1.5+,但自 Spring Framework 4.2 起已弃用。

Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.

以下清单显示了一个示例配置:

The following listing shows an example configuration:

<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 是最基本的类并且始终保证存在。

Note that the target object (businessObjectTarget in the preceding example) must be a prototype. This lets the PoolingTargetSource implementation create new instances of the target to grow the pool as necessary. See the javadoc of AbstractPoolingTargetSource and the concrete subclass you wish to use for information about its properties. maxSize is the most basic and is always guaranteed to be present.

在这种情况下,myInterceptor 是需要在相同的 IoC 上下文中定义的拦截器的名称。但是,您不需要指定要使用池化的拦截器。如果您只想要池化而不要其他建议,请不要设置 interceptorNames 属性。

In this case, myInterceptor is the name of an interceptor that would need to be defined in the same IoC context. However, you need not specify interceptors to use pooling. If you want only pooling and no other advice, do not set the interceptorNames property at all.

您可以通过向 AbstractPoolingTargetSource 类调用便捷方法来配置 Spring,以将任何池化对象强制转换为 org.springframework.aop.target.PoolingConfig 接口,该接口通过 introducing 公开有关池配置和当前大小的信息。您需要定义类似于以下的顾问:

You can configure Spring to be able to cast any pooled object to the org.springframework.aop.target.PoolingConfig interface, which exposes information about the configuration and current size of the pool through an introduction. You need to define an advisor similar to the following:

<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(公开池化对象)中的拦截器名称列表中。

This advisor is obtained by calling a convenience method on the AbstractPoolingTargetSource class, hence the use of MethodInvokingFactoryBean. This advisor’s name (poolConfigAdvisor, here) must be in the list of interceptors names in the ProxyFactoryBean that exposes the pooled object.

强制转换的定义如下:

The cast is defined as follows:

  • 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)

通常不需要合并无状态服务对象。我们不认为它应该是默认选择,因为大多数无状态对象本质上都是线程安全的,如果缓存了资源,实例合并会有问题。

Pooling stateless service objects is not usually necessary. We do not believe it should be the default choice, as most stateless objects are naturally thread-safe, and instance pooling is problematic if resources are cached.

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

Simpler pooling is available by using auto-proxying. You can set the TargetSource implementations used by any auto-proxy creator.

Prototype Target Sources

设置“原型”目标源与设置池化 TargetSource 类似。在这种情况下,将在每次方法调用时创建目标的新实例。虽然在现代 JVM 中创建新对象的花费不高,但连接新对象(满足其 IoC 依赖项)的花费可能更高。因此,您不应该在没有充分理由的情况下使用此方法。

Setting up a “prototype” target source is similar to setting up a pooling TargetSource. In this case, a new instance of the target is created on every method invocation. Although the cost of creating a new object is not high in a modern JVM, the cost of wiring up the new object (satisfying its IoC dependencies) may be more expensive. Thus, you should not use this approach without very good reason.

为此,您可以修改前面所示的 poolTargetSource 定义,如下所示(我们还更改了名称,以提高清晰度):

To do this, you could modify the poolTargetSource definition shown earlier as follows (we also changed the name, for clarity):

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

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

The only property is the name of the target bean. Inheritance is used in the TargetSource implementations to ensure consistent naming. As with the pooling target source, the target bean must be a prototype bean definition.

ThreadLocal Target Sources

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

ThreadLocal target sources are useful if you need an object to be created for each incoming request (per thread that is). The concept of a ThreadLocal provides a JDK-wide facility to transparently store a resource alongside a thread. Setting up a ThreadLocalTargetSource is pretty much the same as was explained for the other types of target source, as the following example shows:

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

ThreadLocal 实例在多线程和多类加载器环境中使用不当时会产生严重的问题(可能会导致内存泄漏)。你应该始终考虑将 ThreadLocal 封装在其他类中,而不要直接使用 ThreadLocal 本身(封装类除外)。此外,你应该始终记住正确设置和取消设置(后者涉及对 ThreadLocal.remove() 的调用)线程的资源本地。在任何情况下都应该取消设置,因为如果不取消设置可能会导致问题行为。Spring 的 ThreadLocal 支持为你执行此操作,并且应该始终考虑有利于在没有其他正确处理代码的情况下使用 ThreadLocal 实例。

ThreadLocal instances come with serious issues (potentially resulting in memory leaks) when incorrectly using them in multi-threaded and multi-classloader environments. You should always consider wrapping a ThreadLocal in some other class and never directly use the ThreadLocal itself (except in the wrapper class). Also, you should always remember to correctly set and unset (where the latter involves a call to ThreadLocal.remove()) the resource local to the thread. Unsetting should be done in any case, since not unsetting it might result in problematic behavior. Spring’s ThreadLocal support does this for you and should always be considered in favor of using ThreadLocal instances without other proper handling code.