使用 TargetSource 实现
Infra 提供了 TargetSource 的概念,由 infra.aop.TargetSource 接口表示。
此接口负责返回实现连接点的“目标对象”。每次 AOP 代理处理方法调用时,都会向 TargetSource 实现请求目标实例。
使用 Infra AOP 的开发人员通常不需要直接使用 TargetSource 实现,但这提供了支持池化、热插拔和其他复杂目标的强大手段。
例如,池化 TargetSource 可以通过使用池来管理实例,为每次调用返回不同的目标实例。
如果不指定 TargetSource,则使用默认实现来包装本地对象。每次调用都会返回相同的目标(如你所料)。
本节其余部分描述了 Infra 提供的标准目标源以及如何使用它们。
| 使用自定义目标源时,你的目标通常需要是原型而不是单例 bean 定义。这允许 Infra 在需要时创建新的目标实例。 |
热插拔目标源
infra.aop.target.HotSwappableTargetSource 的存在是为了让 AOP 代理的目标可以切换,同时让调用者保持对它的引用。
更改目标源的目标会立即生效。HotSwappableTargetSource 是线程安全的。
你可以通过使用 HotSwappableTargetSource 上的 swap() 方法来更改目标,如下例所示:
-
Java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
以下示例显示了所需的 XML 定义:
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="infra.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
<bean id="swappable" class="infra.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>
前面的 swap() 调用更改了可交换 bean 的目标。持有该 bean 引用的客户端不知道更改,但立即开始命中新目标。
虽然此示例未添加任何 advice(使用 TargetSource 不需要添加 advice),但任何 TargetSource 都可以与任意 advice 结合使用。
池化目标源
使用池化目标源提供了类似于无状态会话 EJB 的编程模型,其中维护一个相同实例的池,方法调用将转到池中的空闲对象。
Infra 池化和 SLSB 池化之间的一个关键区别是 Infra 池化可以应用于任何 POJO。与通常的 Infra 一样,此服务可以以非侵入式方式应用。
Infra 提供对 Commons Pool 2.2 的支持,它提供了一个相当高效的池化实现。你需要在应用程序的类路径上包含 commons-pool Jar 才能使用此功能。
你也可以子类化 infra.aop.target.AbstractPoolingTargetSource 以支持任何其他池化 API。
| Commons Pool 1.5+ 也受支持,但从 TODAY Framework 4.0 开始已弃用。 |
以下清单显示了一个示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="infra.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="infra.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
请注意,目标对象(在前面的示例中为 businessObjectTarget)必须是原型。
这使得 PoolingTargetSource 实现可以在必要时创建目标的新实例以增长池。
有关其属性的信息,请参阅 AbstractPoolingTargetSource 的 javadoc 和你希望使用的具体子类。
maxSize 是最基本的,并且始终保证存在。
在这种情况下,myInterceptor 是需要在同一 IoC 上下文中定义的拦截器的名称。
但是,你不需要指定拦截器来使用池化。如果你只想要池化而不需要其他 advice,请根本不要设置 interceptorNames 属性。
你可以配置 Infra 以便能够将任何池化对象转换为 infra.aop.target.PoolingConfig 接口,该接口通过引介公开有关配置和池当前大小的信息。
你需要定义一个类似于以下的 advisor:
<bean id="poolConfigAdvisor" class="infra.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
此 advisor 是通过调用 AbstractPoolingTargetSource 类上的便捷方法获得的,因此使用了 MethodInvokingFactoryBean。
此 advisor 的名称(此处为 poolConfigAdvisor)必须在公开池化对象的 ProxyFactoryBean 中的拦截器名称列表中。
转换定义如下:
-
Java
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
| 池化无状态服务对象通常是不必要的。我们不认为它应该是默认选择,因为大多数无状态对象本质上是线程安全的,如果缓存资源,实例池化会有问题。 |
通过使用自动代理可以使用更简单的池化。你可以设置任何自动代理创建者使用的 TargetSource 实现。
原型目标源
设置“原型”目标源类似于设置池化 TargetSource。在这种情况下,每次方法调用都会创建一个新的目标实例。
虽然在现代 JVM 中创建新对象的成本并不高,但连接新对象(满足其 IoC 依赖项)的成本可能更高。
因此,如果没有充分的理由,你不应该使用这种方法。
为此,你可以按如下方式修改前面显示的 poolTargetSource 定义(为了清楚起见,我们还更改了名称):
<bean id="prototypeTargetSource" class="infra.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 bean 的名称。TargetSource 实现中使用继承以确保一致的命名。
与池化目标源一样,目标 bean 必须是原型 bean 定义。
ThreadLocal 目标源
如果你需要为每个传入请求(即每个线程)创建一个对象,ThreadLocal 目标源非常有用。
ThreadLocal 的概念提供了一个 JDK 范围的设施,可以透明地将资源与线程一起存储。
设置 ThreadLocalTargetSource 与其他类型的目标源的解释几乎相同,如下例所示:
<bean id="threadlocalTargetSource" class="infra.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
当在多线程和多类加载器环境中错误地使用 ThreadLocal 实例时,会出现严重问题(可能导致内存泄漏)。
你应该始终考虑将 ThreadLocal 包装在其他类中,并且永远不要直接使用 ThreadLocal 本身(包装类除外)。
此外,你应该始终记住正确设置和取消设置(后者仅涉及调用 ThreadLocal.set(null))线程本地资源。
在任何情况下都应该取消设置,因为不取消设置可能会导致有问题的行为。Infra ThreadLocal 支持为你执行此操作,
并且应始终优先考虑使用它,而不是在没有其他适当处理代码的情况下使用 ThreadLocal 实例。
|