Skip to content

使用 TargetSource 实现 (Using TargetSource Implementations)

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

使用 Spring AOP 的开发人员通常不需要直接处理 TargetSource 实现,但它提供了一种强大的手段来支持池化(Pooling)热插拔(Hot-swappable)以及其他复杂的目标管理策略。例如,一个池化的 TargetSource 可以在每次调用时通过连接池返回不同的目标实例。

如果你不指定 TargetSource,Spring 会使用默认实现来包装一个本地对象,每次调用都会返回同一个目标对象(正如你所预期的)。

提示

当使用自定义 TargetSource 时,你的目标对象通常需要被定义为 prototype (多例) 而不是 singleton。这允许 Spring 在需要时创建新的目标实例。

热插拔目标源 (Hot-swappable Target Sources)

org.springframework.aop.target.HotSwappableTargetSource 允许动态切换 AOP 代理的目标对象,而调用者可以继续持有对该代理的引用。

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

你可以通过调用 swap() 方法来更改目标对象:

java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
kotlin
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)

XML 配置示例:

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() 调用改变了 swappable Bean 的目标。持有该 Bean 引用的客户端并不会察觉到变化,但由于代理的转发逻辑,它们会立即开始访问新的目标。

池化目标源 (Pooling Target Sources)

使用池化目标源提供的编程模型类似于无状态会话 Bean(SLSB),其中维护着一组相同的实例池,方法调用将被分配给池中的空闲对象。

Spring 池化与 SLSB 池化的一个关键区别在于:Spring 池化可以应用于任何 POJO。这种服务是非侵入性的。

Spring 提供了对 Commons Pool 2 的支持,这是一个相当高效的点池实现。你需要将 commons-pool Jar 包添加到类路径中才能使用此功能。你也可以继承 AbstractPoolingTargetSource 来支持其他池化 API。

配置示例:

xml
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
		scope="prototype">
	<!-- 属性省略 -->
</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>

注意,目标对象必须是 prototype。这使得 PoolingTargetSource 实现能够在需要扩展池大小时创建新的目标实例。

TIP

对于无状态服务对象,通常没有必要进行池化。大多数无状态对象天生就是线程安全的,如果对象内部缓存了资源,实例池化反而可能会带来问题。

Prototype 目标源

设置 “prototype” 目标源与设置池化 TargetSource 类似。在这种情况下,每次方法调用都会创建一个新的目标实例。虽然现代 JVM 创建对象的成本不高,但组装新对象(满足其 IoC 依赖注入)的成本可能更高。因此,除非有非常充分的理由,否则不应使用此方法。

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

ThreadLocal 目标源

如果你需要为每个传入的请求(即每个线程)创建一个对象,ThreadLocal 目标源非常有用。配置方式如下:

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

TIP

如果在使用多线程或多类加载器环境时错误地使用 ThreadLocal 实例,可能会导致内存泄漏。Spring 的 ThreadLocal 支持会自动为你处理资源的设置和取消(调用 remove()),这比手动管理 ThreadLocal 要安全得多。


补充教学

1. 深度剖析:TargetSource 的本质

在绝大多数 AOP 场景中,代理对象内部持有的就是一个固定的单例对象。但如果我们想在“执行拦截”和“触达目标”之间再加一层获取策略TargetSource 就是那个介入点。

你可以把它想象成:

  • 普通代理:代理 -> 拦截器 -> 固定目标。
  • TargetSource 代理:代理 -> 拦截器 -> TargetSource.getTarget() -> 动态目标。

2. 实战场景:蓝绿发布与灰度切换

HotSwappableTargetSource 是实现零停机热切换的利器。

  • 场景:你有一个处理复杂逻辑的服务,现在想在不重启应用的情况下,将所有流量从 V1 版本切换到 V2 版本。
  • 实现:将该服务配置为 HotSwappableTargetSource,流量切换只需一行代码:swapper.swap(v2Instance)

3. 为什么不推荐池化无状态 Bean?

在 EJB 时代,池化是为了管理昂贵的资源连接。现代 Spring 应用中:

  • JVM 性能:短生命周期对象的分配和回收极快。
  • 单例首选:如果你的服务是无状态的,它是天然线程安全的,单例模式性能最高,内存占用最少。
  • 池化的副作用:如果你的对象内部开启了事务或持有了特定资源,池化可能导致资源竞争或事务上下文混乱。
    • 何时该用? 当你的目标对象不是线程安全的(如 StringBuilder 类型,虽然这不是好例子),或者对象创建成本极高且需要限制总数时,才考虑池化。

4. ThreadLocalTargetSource 警示

虽然 Spring 帮你做了垃圾回收(remove),但仍需注意:

  • 线程池污染:由于现代 Web 应用(如 Tomcat)使用线程池,如果一个线程处理完请求后没有彻底清理,下一个请求可能会读取到上一个请求留下的数据。Spring 的实现已经规避了大部分安全问题,但建议只在传递用户上下文(UserContext)等特定透明场景下使用。

Based on Spring Framework.