Appearance
Spring 核心:AOP API
Tip:基于 Spring Core 5.3.30 版本。
1. Spring 中的切点 API
1.1. 概念
Spring 的切点模型使得切点的复用独立于增强类型。你可以用同一个切点针对不同的增强。
org.springframework.aop.Pointcut 接口是核心接口,用于将增强定位到特定的类和方法。完整的接口如下:
Java
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}1
2
3
4
2
3
4
将 Pointcut 接口分成两部分可以复用类匹配部分和方法匹配部分,并进行细粒度的组合操作(例如,与另一个方法匹配器执行 “并集” 操作)。
ClassFilter 接口用于将切点限制在给定的目标类集合中。如果 matches() 方法始终返回 true,则所有目标类都会被匹配。下面是 ClassFilter 接口定义的列表:
Java
public interface ClassFilter {
boolean matches(Class clazz);
}1
2
3
2
3
MethodMatcher 接口通常更为重要。完整的接口如下:
Java
public interface MethodMatcher {
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}1
2
3
4
5
2
3
4
5
matches(Method, Class) 方法用于测试此切点是否匹配目标类上的给定方法。当创建 AOP 代理时,可以执行此评估,以避免在每次方法调用上都需要进行测试。如果给定方法的两参数 matches 方法返回 true,并且 MethodMatcher 的 isRuntime() 方法返回 true,则在每次方法调用时都会调用三参数 matches 方法。这让切点可以在目标增强开始之前立即查看传递给方法调用的参数。
大多数 MethodMatcher 实现是静态的,意味着它们的 isRuntime() 方法返回 false。在这种情况下,永远不会调用三参数 matches 方法。
Note:如果可能的话,尽量使切点静态化,这样 AOP 框架在创建 AOP 代理时就可以缓存切点评估的结果。
1.2. 对切点的操作
Spring 支持对切点进行操作(特别是并集和交集)。
并集意味着匹配任一切点的方法。交集意味着匹配两个切点的方法。并集通常更有用。你可以通过使用 org.springframework.aop.support.Pointcuts 类中的静态方法或者使用同一包中的 ComposablePointcut 类来组合切点。然而,使用 AspectJ 切点表达式通常是一种更简单的方法。
1.3. AspectJ 表达式切点
自 2.0 版本以来,Spring 使用的最重要的切点类型是 org.springframework.aop.aspectj.AspectJExpressionPointcut。这是一个使用 AspectJ 提供的库来解析 AspectJ 切点表达式字符串的切点。
有关支持的 AspectJ 切点原语的讨论,请参阅前一章。
1.4. 方便的切点实现
Spring 提供了几种方便的切点实现。你可以直接使用其中的一些,其他的则是打算在特定于应用的切点中被子类化。
1.4.1. 静态切点
静态切点基于方法和目标类,并且不能考虑方法的参数。静态切点足以满足大多数用途,并且是最佳选择。Spring 只能在方法第一次被调用时评估一次静态切点。之后,每次方法调用都不需要再次评估切点。
本节的其余部分描述了 Spring 包含的一些静态切点实现。
1.4.1.1. 正则表达式切点
指定静态切点的一个明显方式是使用正则表达式。除了 Spring 之外,还有几个 AOP 框架也支持这种方式。org.springframework.aop.support.JdkRegexpMethodPointcut 是一个通用的正则表达式切点,它使用 JDK 中的正则表达式支持。
使用 JdkRegexpMethodPointcut 类,您可以提供一系列模式字符串。如果其中任何一个匹配,切点评估为 true。(因此,结果切点实际上是所指定模式的并集。)
以下示例显示了如何使用 JdkRegexpMethodPointcut:
XML
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Spring 提供了一个名为 RegexpMethodPointcutAdvisor 的便利类,它允许我们引用一个 Advice(请记住,Advice 可以是拦截器、前置增强、抛出增强等)。在幕后,Spring 使用的是 JdkRegexpMethodPointcut。使用 RegexpMethodPointcutAdvisor 可以简化装配,因为这个 Bean 同时封装了切点和增强,如下例所示:
XML
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
你可以将 RegexpMethodPointcutAdvisor 与任何类型的 Advice 一起使用。
1.4.1.2. 基于属性的切点
静态切点的一个重要类型是元数据驱动的切点。它使用元数据属性的值(通常是源级别的元数据)。
1.4.2. 动态切点
动态切点的评估成本比静态切点更高。它们考虑了方法参数以及静态信息。这意味着它们必须在每次方法调用时进行评估,而且结果不能被缓存,因为参数会变化。
主要的例子是控制流切点。
1.4.2.1. 控制流切点
Spring 的控制流切点在概念上类似于 AspectJ 的 cflow 切点,尽管功能较弱。(目前还没有办法指定一个切点在由另一个切点匹配的连接点下面运行)。控制流切点匹配当前的调用栈。例如,如果连接点是由 com.mycompany.web 包中的方法或 SomeCaller 类调用的,它可能会触发。控制流切点是通过使用 org.springframework.aop.support.ControlFlowPointcut 类来指定的。
Note:控制流切点在运行时的评估成本比其他动态切点还要高得多。在 Java 1.4 中,其成本大约是其他动态切点的五倍。
1.5. 切点超类
Spring 提供了有用的切点超类,以帮助你实现自己的切点。
因为静态切点最有用,你可能应该继承 StaticMethodMatcherPointcut。这只需要实现一个抽象方法(尽管你可以重写其他方法以自定义行为)。下面的示例展示了如何继承 StaticMethodMatcherPointcut:
Java
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}1
2
3
4
5
2
3
4
5
对于动态切点,也有超类。你可以将自定义切点与任何增强类型一起使用。
1.6. 自定义切点
因为在 Spring AOP 中,切点是 Java 类,而不是语言特性(如在 AspectJ 中),你可以声明自定义切点,无论是静态的还是动态的。Spring 中的自定义切点可以任意复杂。然而,如果可以的话,我们推荐使用 AspectJ 的切点表达式语言。
Note:Spring 的后续版本可能会提供对 JAC 提供的 “语义切点(Semantic Pointcuts)” 的支持,例如,“改变目标对象中实例变量的所有方法”。
2. Spring 中的增强 API
现在我们可以来看看 Spring AOP 是如何处理增强的。
2.1. 增强生命周期
每个增强都是一个 Spring Bean。增强实例可以跨所有被增强对象共享,也可以对每个被增强对象唯一。这对应于按类或按实例的增强。
按类增强是最常用的。它适用于通用增强,例如事务增强器。这些增强不依赖于被代理对象的状态,也不会添加新状态。它们只是作用于方法和参数。
按实例增强适用于引入,以支持混入。在这种情况下,增强为被代理对象添加状态。
您可以在同一个 AOP 代理中混合使用共享增强和按实例增强。
2.2. Spring 中的增强类型
Spring 提供了多种增强类型,并且可扩展以支持任意增强类型。本节描述了基本概念和标准增强类型。
2.2.1. 环绕增强拦截
Spring 中最基本的增强类型是环绕增强。
Spring 符合 AOP 联盟接口,该接口使用方法拦截来实现环绕增强。实现 MethodInterceptor 接口并且实现环绕增强的类还应该实现以下接口:
Java
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}1
2
3
2
3
invoke() 方法的 MethodInvocation 参数公开了被调用的方法、目标连接点、AOP 代理以及方法的参数。invoke() 方法应该返回调用的结果:连接点的返回值。
下面的例子展示了一个简单的 MethodInterceptor 实现:
Java
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
请注意对 MethodInvocation 的 proceed() 方法的调用。这会沿着拦截器链向连接点进行。大多数拦截器调用这个方法并返回它的返回值。然而,MethodInterceptor(像任何环绕增强一样)可以选择返回一个不同的值,或者抛出一个异常,而不是调用 proceed 方法。但是,除非有充分的理由,否则你不应该这样做。
Note:
MethodInterceptor的实现提供了与其他符合 AOP 联盟的 AOP 实现的互操作性。本节剩余部分讨论的其他增强类型实现了常见的 AOP 概念,但以 Spring 特有的方式实现。尽管使用最具体的增强类型有其优势,但如果你可能希望在其他 AOP 框架中运行切面(Aspect),请坚持使用MethodInterceptor环绕增强。请注意,目前不同框架之间的切点(Pointcut)是不可互操作的,而且 AOP 联盟目前没有定义切点的接口。
2.2.2. 前置增强
一种更简单的增强类型是前置增强。由于它仅在进入方法之前被调用,因此不需要 MethodInvocation 对象。
前置增强的主要优点是无需调用 proceed() 方法,因此不存在无意中未能沿拦截器链继续执行的可能性。
以下清单展示了 MethodBeforeAdvice 接口:
Java
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}1
2
3
2
3
Spring 的 API 设计允许字段前置增强,尽管通常的对象适用于字段拦截,但 Spring 很可能永远不会实现它。
请注意,返回类型是 void。前置增强可以在连接点运行之前插入自定义行为,但不能改变返回值。如果前置增强抛出异常,它会停止拦截器链的进一步执行。异常会沿着拦截器链传播回去。如果它是未检查的,或者是在被调用方法的签名上,它会直接传递给客户端。否则,它会被 AOP 代理包装成一个未检查的异常。
下面的例子展示了 Spring 中的一个前置增强,它计算所有方法调用的次数:
Java
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
2.2.3. 异常增强
异常增强在连接点抛出异常后返回时被调用。Spring 提供了类型化的异常增强。请注意,这意味着 org.springframework.aop.ThrowsAdvice 接口不包含任何方法。它是一个标记接口,用于标识给定的对象实现了一个或多个类型化的异常增强方法。这些方法应该具有以下形式:
ABNF
"afterThrowing(" [method "," args "," target ","] subclass-of-throwable ")"只有最后一个参数是必需的。方法签名可以根据增强方法是否对方法和参数感兴趣而有一个或四个参数。接下来的两个清单展示了异常增强的类示例。
如果抛出 RemoteException(包括从子类抛出),则会调用以下增强:
Java
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}1
2
3
4
5
2
3
4
5
与前一个增强不同,下一个示例声明了四个参数,因此它可以访问被调用的方法、方法参数和目标对象。如果抛出 ServletException,则会调用以下增强:
Java
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}1
2
3
4
5
2
3
4
5
最后一个示例展示了如何在这两个方法在单个类中使用,以处理 RemoteException 和 ServletException。任意数量的异常增强方法可以在单个类中组合使用。以下清单展示了最后一个示例:
Java
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Warning:如果一个异常增强方法本身抛出异常,它会覆盖原始异常(也就是说,它改变了抛给用户的异常)。覆盖的异常通常是
RuntimeException,它与任何方法签名都兼容。然而,如果一个异常增强方法抛出一个检查型异常,它必须匹配目标方法的声明异常,因此在某种程度上与特定的目标方法签名耦合。不要抛出一个与目标方法签名不兼容的未声明检查型异常!
2.2.4. 返回后增强
Spring 中的返回后增强必须实现 org.springframework.aop.AfterReturningAdvice 接口,如下列清单所示:
Java
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}1
2
3
4
2
3
4
返回后增强可以访问返回值(它不能修改)、被调用的方法、方法的参数和目标对象。
以下返回后增强计算所有成功的方法调用次数,这些调用没有抛出异常:
Java
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}
public int getCount() {
return count;
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
这种增强不会改变执行路径。如果它抛出异常,异常会沿着拦截器链向上抛出,而不是返回值。
2.2.5. 引入增强
Spring 将引入增强视为一种特殊的拦截增强。
引入需要 IntroductionAdvisor 和 IntroductionInterceptor 来实现以下接口:
Java
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}1
2
3
2
3
从 AOP 联盟 MethodInterceptor 接口继承的 invoke() 方法必须实现引入。也就是说,如果被调用的方法是引入的接口上的方法,引入拦截器负责处理方法调用 —— 它不能调用 proceed()。
引入增强不能与任何切点一起使用,因为它只应用于类级别,而不是方法级别。您只能使用引入增强与 IntroductionAdvisor 一起使用,该 IntroductionAdvisor 具有以下方法:
Java
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
引入增强没有 MethodMatcher,因此也没有与之关联的切点。只有类过滤是合乎逻辑的。
getInterfaces() 方法返回此增强器引入的接口。
validateInterfaces() 方法在内部使用,以查看配置的 IntroductionInterceptor 是否能够实现引入的接口。
考虑来自 Spring 测试套件的一个示例,假设我们想要将以下接口引入一个或多个对象:
Java
public interface Lockable {
void lock();
void unlock();
boolean locked();
}1
2
3
4
5
2
3
4
5
这说明了一个混入(Mixin)。我们希望能够将被增强的对象转型为 Lockable,无论它们的类型是什么,并调用 lock 和 unlock 方法。如果我们调用 lock() 方法,我们希望所有的 Setter 方法都抛出 LockedException。因此,我们可以添加一个切面,使对象在不知情的情况下变得不可变:这是 AOP 的一个很好的例子。
首先,我们需要一个 IntroductionInterceptor 来完成繁重的工作。在这种情况下,我们扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor 这个便利类。我们可以直接实现 IntroductionInterceptor,但在大多数情况下,使用 DelegatingIntroductionInterceptor 是最好的。
DelegatingIntroductionInterceptor 的设计目的是将引入委托给实际实现了被引入接口的对象,隐藏了使用拦截来实现这一点的事实。你可以使用构造函数参数将委托设置为任何对象。默认的委托(当使用无参数构造函数时)是 this。因此,在下一个例子中,委托是 DelegatingIntroductionInterceptor 的子类 LockMixin。给定一个委托(默认为自身),DelegatingIntroductionInterceptor 实例会查找委托实现的所有接口(除了 IntroductionInterceptor),并支持针对它们的任何引入。像 LockMixin 这样的子类可以调用 suppressInterface(Class intf) 方法来抑制不应该被暴露的接口。然而,无论一个 IntroductionInterceptor 准备支持多少接口,使用的 IntroductionAdvisor 控制了实际暴露的接口是哪些。一个被引入的接口隐藏了目标实现的同一接口。
因此,LockMixin 扩展了 DelegatingIntroductionInterceptor 并实现了 Lockable 本身。超类自动发现 Lockable 可以被支持引入,所以我们不需要指定这一点。我们可以以这种方式引入任意数量的接口。
注意使用了 locked 实例变量。这有效地向目标对象中保存的状态添加了额外的状态。
下面的例子显示了 LockMixin 类的示例:
Java
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
通常,你不需要重写 invoke() 方法。DelegatingIntroductionInterceptor 的实现(如果方法被引入,则调用委托方法,否则继续进行连接点)通常就足够了。在当前的情况下,我们需要添加一个检查:如果处于锁定模式,不能调用任何 Setter 方法。
所需的引入只需要持有一个独特的 LockMixin 实例,并指定被引入的接口(在这种情况下,只有 Lockable)。一个更复杂的例子可能会取得对引入拦截器的引用(这将被定义为原型)。在这种情况下,对于 LockMixin 没有相关的配置,所以我们通过使用 new 来创建它。下面的例子显示了我们的 LockMixinAdvisor 类:
Java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}1
2
3
4
5
2
3
4
5
我们可以非常简单地应用这个增强器,因为它不需要任何配置。(然而,没有 IntroductionAdvisor,就无法使用 IntroductionInterceptor。)像通常的引入一样,增强器必须是每个实例的,因为它是有状态的。我们需要为每个被增强的对象提供不同的 LockMixinAdvisor 实例,因此也需要不同的 LockMixin。增强器构成了被增强对象状态的一部分。
我们可以通过使用 Advised.addAdvisor() 方法或者(推荐的方式)在 XML 配置中,像应用任何其他增强器一样应用这个增强器。所有下面讨论的代理创建选择,包括 “自动代理创建者”,都能正确处理引入和有状态的混入。
3. Spring 中的增强器 API
在 Spring 中,增强器是一个切面,它只包含一个与切点表达式关联的增强对象。
除了引入的特殊情况外,任何增强器都可以与任何增强一起使用。org.springframework.aop.support.DefaultPointcutAdvisor 是最常用的增强器类。它可以与 MethodInterceptor、BeforeAdvice 或 ThrowsAdvice 一起使用。
在 Spring 中,可以在同一个 AOP 代理中混合使用增强器和增强类型。例如,你可以在一个代理配置中使用环绕增强、抛出增强和前置增强。Spring 会自动创建必要的拦截器链。
4. 使用 ProxyFactoryBean 创建 AOP 代理
如果你使用 Spring IoC 容器(ApplicationContext 或 BeanFactory)来管理你的业务对象(你应该这么做!),你会想要使用 Spring 的 AOP FactoryBean 实现之一。(记住,工厂 Bean 引入了一层间接性,让它可以创建不同类型的对象。)
Note:Spring AOP 支持也在底层使用了工厂 Bean。
在 Spring 中,创建 AOP 代理的基本方式是使用 org.springframework.aop.framework.ProxyFactoryBean。这提供了对切点、任何适用的增强以及它们的排序的完全控制。然而,如果你不需要这样的控制,那么还有更简单的选择。
4.1. 基础
ProxyFactoryBean,就像其他 Spring 的 FactoryBean 实现一样,引入了一个间接级别。如果你定义了一个名为 foo 的 ProxyFactoryBean,那么引用 foo 的对象看到的不是 ProxyFactoryBean 实例本身,而是由 ProxyFactoryBean 中的 getObject() 方法实现创建的对象。这个方法创建了一个包装目标对象的 AOP 代理。
使用 ProxyFactoryBean 或其他 IoC 感知类来创建 AOP 代理的最重要的好处之一是,增强和切点也可以由 IoC 管理。这是一个强大的特性,使得某些难以通过其他 AOP 框架实现的方法变得可能。例如,一个增强本身可能引用应用程序对象(除了目标,这应该在任何 AOP 框架中都可用),从而受益于依赖注入提供的所有可插拔性。
4.2. JavaBean 属性
与 Spring 提供的大多数 FactoryBean 实现一样,ProxyFactoryBean 类本身是一个 JavaBean。它的属性用于:
- 指定你想要代理的目标;
- 指定是否使用 CGLIB(稍后将进行描述,也请参见基于 JDK 和 CGLIB 的代理);
一些关键属性是从 org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)继承的。这些关键属性包括以下内容:
proxyTargetClass:如果要代理的是目标类,而不是目标类的接口,则为true。如果此属性值设置为true,则会创建 CGLIB 代理(但也请参见基于 JDK 和 CGLIB 的代理);optimize:控制是否对通过 CGLIB 创建的代理应用积极的优化。除非你完全理解相关的 AOP 代理如何处理优化,否则你不应轻易使用此设置。这目前仅用于 CGLIB 代理。对 JDK 动态代理没有影响;frozen:如果代理配置被冻结,那么不再允许对配置进行更改。这既有助于微小的优化,也适用于那些你不希望调用者在代理创建后能够操作代理(通过Advised接口)的情况。此属性的默认值为false,因此允许进行更改(例如添加额外的增强);exposeProxy:确定是否应在ThreadLocal中公开当前的代理,以便目标可以访问。如果目标需要获取代理,并且exposeProxy属性设置为true,那么目标可以使用AopContext.currentProxy()方法;
特定于 ProxyFactoryBean 的其他属性包括以下内容:
proxyInterfaces:一个接口名称的String数组。如果没有提供此项,则会使用针对目标类的 CGLIB 代理(但也请参见基于 JDK 和 CGLIB 的代理);interceptorNames:要应用的增强器、拦截器或其他增强名称的String数组。顺序很重要,按照先到先得的原则。也就是说,列表中的第一个拦截器是第一个能够拦截调用的;这些名称是当前工厂中的 Bean 名称,包括祖先工厂中的 Bean 名称。你不能在这里提及 Bean 引用,因为这样做会导致
ProxyFactoryBean忽略增强的单例设置。你可以在拦截器名称后面加上一个星号(
*)。这样做会导致所有以星号前面的部分开头的增强 Bean 名称都被应用。你可以在使用 “全局” 增强器中找到使用此功能的示例。singleton:无论调用getObject()方法多少次,工厂是否应返回单个对象。几个FactoryBean实现提供了这样的方法。默认值为true。如果你想要使用有状态的增强,例如,对于有状态的混入 —— 使用原型增强和false的singleton值;
4.3. 基于 JDK 和 CGLIB 的代理
本节作为 ProxyFactoryBean 如何选择为特定目标对象(即将被代理的对象)创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档。
Note:
ProxyFactoryBean创建基于 JDK 或基于 CGLIB 的代理的行为在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。ProxyFactoryBean现在展示了与TransactionProxyFactoryBean类相似的自动检测接口的语义。
如果要被代理的目标对象的类(以下简称目标类)没有实现任何接口,那么就会创建一个基于 CGLIB 的代理。这是最简单的情况,因为 JDK 代理是基于接口的,没有接口意味着 JDK 代理甚至不可能。你可以插入目标 Bean 并通过设置 interceptorNames 属性来指定拦截器列表。请注意,即使 ProxyFactoryBean 的 proxyTargetClass 属性已经设置为 false,也会创建一个基于 CGLIB 的代理。(这样做没有意义,最好从 Bean 定义中删除,因为它会令人困惑。)
如果目标类实现了一个(或多个)接口,那么创建的代理的类型取决于 ProxyFactoryBean 的配置。
如果 ProxyFactoryBean 的 proxyTargetClass 属性已经设置为 true,那么就会创建一个基于 CGLIB 的代理。这是有意义的,并且符合最小惊讶原则。即使 ProxyFactoryBean 的 proxyInterfaces 属性已经设置为一个或多个完全限定的接口名称,由于 proxyTargetClass 属性设置为 true,所以基于 CGLIB 的代理仍然有效。
如果 ProxyFactoryBean 的 proxyInterfaces 属性已经设置为一个或多个完全限定的接口名称,那么就会创建一个基于 JDK 的代理。创建的代理实现了在 proxyInterfaces 属性中指定的所有接口。如果目标类恰好实现了比 proxyInterfaces 属性中指定的更多的接口,那么这是非常好的,但是返回的代理并没有实现那些额外的接口。
如果 ProxyFactoryBean 的 proxyInterfaces 属性没有被设置,但是目标类确实实现了一个(或多个)接口,那么 ProxyFactoryBean 会自动检测到目标类确实实现了至少一个接口,并创建一个基于 JDK 的代理。实际被代理的接口是目标类实现的所有接口。实际上,这与向 proxyInterfaces 属性提供目标类实现的每一个接口的列表是一样的。然而,这显著减少了工作量,并且不容易出现打字错误。
4.4. 代理接口
考虑一个 ProxyFactoryBean 的简单示例。这个示例涉及:
- 一个被代理的目标 Bean。这是示例中的
personTargetBean 定义; - 一个用于提供增强的增强器和拦截器;
- 一个 AOP 代理 Bean 定义,用于指定目标对象(
personTargetBean)、要代理的接口和要应用的增强;
以下清单显示了示例:
XML
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
请注意,interceptorNames 属性接受一个 String 列表,该列表包含当前工厂中的拦截器或增强器的 Bean 名称。你可以使用增强器、拦截器、前置、返回后和异常增强对象。增强器的顺序是有意义的。
Tip:你可能会想知道为什么这个列表不持有 Bean 的引用。原因是,如果
ProxyFactoryBean的单例属性被设置为false,它必须能够返回独立的代理实例。如果任何增强器本身就是一个原型,那么就需要返回一个独立的实例,所以有必要能够从工厂获取原型的一个实例。仅仅持有一个引用是不够的。
如前面所示的 person Bean 定义,可以用来替代 Person 的实现,如下所示:
Java
Person person = (Person) factory.getBean("person");在同一 IoC 上下文中的其他 Bean 可以像普通 Java 对象一样,对它表达强类型的依赖。下面的示例展示了如何做到这一点:
XML
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>1
2
3
2
3
在这个示例中,PersonUser 类公开了一个类型为 Person 的属性。就它而言,AOP 代理可以透明地替代 “真实” 的 person 实现。然而,它的类将是一个动态代理类。将它转型为 Advised 接口(稍后讨论)是可能的。
你可以通过使用匿名内部 Bean 来隐藏目标和代理之间的区别。只有 ProxyFactoryBean 的定义是不同的。下面的示例展示了如何使用匿名内部 Bean:
XML
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用匿名内部 Bean 的优点是只有一个类型为 Person 的对象。这在我们想防止应用程序上下文的用户获取对未增强(un-advised)对象的引用或需要避免与 Spring IoC 自动装配的任何歧义时非常有用。也可以说,ProxyFactoryBean 定义是自包含的,这是一个优点。然而,有时能够从工厂获取未增强的目标实际上可能是一个优点(例如,在某些测试场景中)。
4.5. 代理类
如果你需要代理一个类,而不是一个或多个接口,该怎么办?
假设在我们之前的示例中,没有 Person 接口。我们需要增强一个名为 Person 的类,它没有实现任何业务接口。在这种情况下,你可以配置 Spring 使用 CGLIB 代理而不是动态代理。为此,将之前显示的 ProxyFactoryBean 上的 proxyTargetClass 属性设置为 true。虽然最好是针对接口编程而不是类,但是增强不实现接口的类的能力在处理遗留代码时可能会有用。(一般来说,Spring 不是规定性的。虽然它使得应用良好的实践变得容易,但它避免强制采用特定的方法。)
如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你有接口。
CGLIB 代理通过在运行时生成目标类的子类来工作。Spring 配置这个生成的子类将方法调用委托给原始目标。子类被用来实现装饰器模式,织入增强。
CGLIB 代理通常应该对用户透明。然而,有一些问题需要考虑:
final方法不能被增强,因为它们不能被覆盖;你无需将 CGLIB 添加到你的类路径中。从 Spring 3.2 开始,CGLIB 被重新打包并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 工作 “开箱即用”,就像 JDK 动态代理一样;
CGLIB 代理和动态代理之间的性能差异很小。在这种情况下,性能不应该是决定性的考虑因素。
4.6. 使用 “全局” 增强器
通过在拦截器名称后附加一个星号,所有与星号前部分匹配的 Bean 名称的增强器都将被添加到增强器链中。如果你需要添加一组标准的 “全局” 增强器,这可能会很方便。以下示例定义了两个全局增强器:
XML
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
5. 简明的代理定义
在定义事务代理时,你可能会遇到许多相似的代理定义。使用父子 Bean 定义以及内部 Bean 定义,可以得到更清晰、更简洁的代理定义。
首先,我们为代理创建一个父模板 Bean 定义,如下所示:
XML
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
这个永远不会被实例化,所以它实际上可以是不完整的。然后,需要创建的每一个代理都是一个子 Bean 定义,它将代理的目标包装为一个内部 Bean 定义,因为目标本身从来不会被单独使用。下面的示例展示了这样一个子 Bean:
XML
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>1
2
3
4
5
6
2
3
4
5
6
你可以覆盖父模板的属性。在下面的示例中,我们覆盖了事务传播设置:
XML
<bean id="mySpecialService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MySpecialServiceImpl">
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
请注意,在父 Bean 示例中,我们明确地将父 Bean 定义标记为抽象的,通过将 abstract 属性设置为 true,如前所述,因此它可能实际上永远不会被实例化。应用上下文(但不是简单的 Bean 工厂)默认预先实例化所有的单例。因此,至少对于单例 Bean 来说,如果你有一个(父)Bean 定义,你打算只作为模板使用,并且这个定义指定了一个类,你必须确保将 abstract 属性设置为 true。否则,应用上下文实际上会尝试预先实例化它。
6. 使用 ProxyFactory 以编程方式创建 AOP 代理
使用 Spring 编程创建 AOP 代理是很容易的。这让你可以在不依赖 Spring IoC 的情况下使用 Spring AOP。
目标对象实现的接口会被自动代理。下面的列表展示了为一个目标对象创建一个代理,带有一个拦截器和一个增强器:
Java
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();1
2
3
4
2
3
4
第一步是构造一个类型为 org.springframework.aop.framework.ProxyFactory 的对象。你可以用一个目标对象来创建它,就像前面的例子那样,或者在另一个构造函数中指定要代理的接口。
你可以添加增强(拦截器是一种特殊的增强)、增强器,或者两者都添加,并且可以在 ProxyFactory 的生命周期中操纵它们。如果你添加了一个 IntroductionInterceptionAroundAdvisor,你可以让代理实现额外的接口。
ProxyFactory 上也有一些方便的方法(从 AdvisedSupport 继承)让你添加其他类型的增强,比如前置和抛出后增强。AdvisedSupport 是 ProxyFactory 和 ProxyFactoryBean 的超类。
Note:将 AOP 代理创建与 IoC 框架集成在大多数应用程序中都是最佳实践。我们建议你使用 AOP 将配置从 Java 代码中外部化,这是你通常应该做的。
7. 操作被增强的对象
无论你如何创建 AOP 代理,你都可以通过使用 org.springframework.aop.framework.Advised 接口来操作它们。任何 AOP 代理都可以被强制转换为这个接口,无论它实现了哪些其他的接口。这个接口包括以下的方法:
Java
Advisor[] getAdvisors();
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
int indexOf(Advisor advisor);
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
void removeAdvisor(int index) throws AopConfigException;
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
boolean isFrozen();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
getAdvisors() 方法返回了每一个已经被添加到工厂的增强器、拦截器或其他增强类型的增强器。如果你添加了一个增强器,那么在这个索引返回的增强器就是你添加的对象。如果你添加了一个拦截器或其他增强类型,Spring 会将其包装在一个切点始终返回 true 的增强器中。因此,如果你添加了一个 MethodInterceptor,那么在这个索引返回的增强器就是一个返回你的 MethodInterceptor 和一个匹配所有类和方法的切点的 DefaultPointcutAdvisor。
addAdvisor() 方法可以用来添加任何增强器。通常,持有切点和增强的增强器是通用的 DefaultPointcutAdvisor,你可以将其与任何增强或切点一起使用(但不能用于引入)。
默认情况下,即使已经创建了代理,也可以添加或删除增强器或拦截器。唯一的限制是,无法添加或删除引入增强器,因为工厂的现有代理不会显示接口变化。(你可以从工厂获取一个新的代理以避免这个问题。)
以下示例显示了将 AOP 代理转换为 Advised 接口,并检查和操作其增强:
Java
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());
// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Note:对于在生产中修改业务对象的增强是否明智,这是有争议的,尽管毫无疑问,有合理的使用案例。然而,在开发中(例如,在测试中),这可能非常有用。我们有时发现,能够以拦截器或其他增强的形式添加测试代码,进入我们想要测试的方法调用,这是非常有用的。(例如,增强可以进入为该方法创建的事务,在标记事务以进行回滚之前,可能运行 SQL 来检查数据库是否已正确更新。)
根据你创建代理的方式,你通常可以设置一个 frozen 标志。在这种情况下,Advised 的 isFrozen() 方法返回 true,任何尝试通过添加或删除来修改增强的操作都会导致 AopConfigException。在某些情况下,冻结被增强对象的状态的能力是有用的(例如,防止调用代码删除安全拦截器)。
8. 使用 "auto-proxy" 设施
到目前为止,我们已经考虑了使用 ProxyFactoryBean 或类似的工厂 Bean 显式创建 AOP 代理。
Spring 也允许我们使用 “自动代理” Bean 定义,它可以自动代理选定的 Bean 定义。这是建立在 Spring 的 “Bean 后处理器” 基础设施上的,它使得在容器加载时可以修改任何 Bean 定义。
在这个模型中,你在 XML Bean 定义文件中设置一些特殊的 Bean 定义来配置自动代理基础设施。这让你可以声明符合自动代理条件的目标。你不需要使用 ProxyFactoryBean。
有两种方法可以做到这一点:
通过使用引用当前上下文中特定 Bean 的自动代理创建器;
值得单独考虑的自动代理创建的特殊情况:由源级元数据属性(source-level metadata attributes)驱动的自动代理创建;
8.1. 自动代理 Bean 定义
这一部分将介绍由 org.springframework.aop.framework.autoproxy 包提供的自动代理创建器。
8.1.1. BeanNameAutoProxyCreator
BeanNameAutoProxyCreator 类是一个 BeanPostProcessor,它会自动为与字面值或通配符匹配的 Bean 名称创建 AOP 代理。以下示例展示了如何创建一个 BeanNameAutoProxyCreator Bean:
XML
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
与 ProxyFactoryBean 一样,这里有一个 interceptorNames 属性,而不是拦截器列表,以允许原型增强器的正确行为。命名的 “拦截器” 可以是增强器或任何增强类型。
与自动代理一般,使用 BeanNameAutoProxyCreator 的主要目的是将相同的配置一致地应用到多个对象上,配置量最小。它是将声明式事务应用到多个对象的热门选择。
Bean 定义的名称匹配,例如前面示例中的 jdkMyBean 和 onlyJdk,都是具有目标类的普通旧 Bean 定义。AOP 代理由 BeanNameAutoProxyCreator 自动创建。相同的增强被应用到所有匹配的 Bean 上。注意,如果使用的是增强器(而不是前面示例中的拦截器),切点可能会对不同的 Bean 有不同的应用。
8.1.2. DefaultAdvisorAutoProxyCreator
更通用且极其强大的自动代理创建器是 DefaultAdvisorAutoProxyCreator。它会自动地将符合条件的增强器应用到当前上下文中,无需在自动代理增强器的 Bean 定义中包含特定的 Bean 名称。它提供了与 BeanNameAutoProxyCreator 相同的一致配置和避免重复的优点。
使用这种机制涉及:
指定一个
DefaultAdvisorAutoProxyCreatorBean 定义;在相同或相关的上下文中指定任意数量的增强器。注意,这些必须是增强器,而不是拦截器或其他增强。这是必要的,因为必须有一个切点来评估,以检查每个增强对候选 Bean 定义的适用性;
DefaultAdvisorAutoProxyCreator 会自动评估每个增强器中包含的切点,以查看它应该将哪些(如果有的话)增强应用到每个业务对象(例如,示例中的 businessObject1 和 businessObject2)。
这意味着任意数量的增强器可以自动应用到每个业务对象。如果在任何增强器的切点中都没有匹配到业务对象的任何方法,那么该对象就不会被代理。随着新的业务对象的 Bean 定义被添加,如果需要,它们会自动被代理。
总的来说,自动代理有一个优点,那就是使得调用者或依赖无法获取未被增强的对象。在这个 ApplicationContext 上调用 getBean("businessObject1") 返回的是一个 AOP 代理,而不是目标业务对象。(早些时候展示的 “内部 Bean” 也提供了这个好处。)
以下示例创建了一个 DefaultAdvisorAutoProxyCreator Bean 和本节讨论的其他元素:
XML
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
如果你想将相同的增强一致地应用到许多业务对象上,DefaultAdvisorAutoProxyCreator 是非常有用的。一旦基础设施定义就位,你可以添加新的业务对象,而无需包含特定的代理配置。你也可以轻松地添加额外的切面(例如,跟踪或性能监控切面),对配置的改变最小。
DefaultAdvisorAutoProxyCreator 提供了过滤支持(通过使用命名约定,使得只有某些增强器被评估,这允许在同一个工厂中使用多个不同配置的 AdvisorAutoProxyCreators)和排序。如果这是一个问题,增强器可以实现 org.springframework.core.Ordered 接口以确保正确的排序。在前面的示例中使用的 TransactionAttributeSourceAdvisor 有一个可配置的顺序值。默认设置是无序的。
9. 使用 TargetSource 实现
Spring 提供了一个目标源(Targe tSource)的概念,它在 org.springframework.aop.TargetSource 接口中表达。这个接口负责返回实现连接点的 “目标对象”。每次 AOP 代理处理方法调用时,都会要求 TargetSource 实现返回一个目标实例。
使用 Spring AOP 的开发者通常不需要直接使用 TargetSource 实现,但这提供了一种强大的方式来支持池化、热插拔和其他复杂的目标。例如,一个池化的 TargetSource 可以通过使用池来管理实例,为每次调用返回一个不同的目标实例。
如果你没有指定 TargetSource,则会使用默认实现来包装本地对象。每次调用返回的目标都是相同的(正如你所期望的)。
本节的其余部分描述了 Spring 提供的标准目标源以及你如何使用它们。
Note:当使用自定义目标源时,你的目标通常需要是原型,而不是单例 Bean 定义。这允许 Spring 在需要时创建一个新的目标实例。
9.1. 热插拔目标源
org.springframework.aop.target.HotSwappableTargetSource 存在的目的是让 AOP 代理的目标可以在让调用者保持对它的引用的同时进行切换。
更改目标源的目标会立即生效。HotSwappableTargetSource 是线程安全的。
你可以通过在 HotSwappableTargetSource 上使用 swap() 方法来更改目标,如下例所示:
Java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);1
2
2
以下示例显示了所需的 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>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
前面的 swap() 调用更改了可交换 Bean 的目标。持有该 Bean 引用的客户端并不知道这个变化,但会立即开始访问新的目标。
尽管这个示例没有添加任何增强(使用 TargetSource 不必添加增强),但任何 TargetSource 都可以与任意增强一起使用。
9.2. 池化目标源
使用池化目标源提供了一个类似于无状态会话 EJB 的编程模型,在该模型中,维护了一个由相同实例组成的池,方法调用会转到池中的空闲对象。
Spring 池化和 SLSB 池化之间的关键区别在于,Spring 池化可以应用于任何 POJO。就像 Spring 一般,这个服务可以以非侵入性的方式应用。
Spring 提供了对 Commons Pool 2.2 的支持,它提供了一个相当高效的池化实现。你需要在你的应用程序的类路径上有 commons-pool Jar 才能使用这个特性。你也可以继承 org.springframework.aop.target.AbstractPoolingTargetSource 来支持任何其他的池化 API。
Note:虽然 Commons Pool 1.5+ 也得到了支持,但从 Spring Framework 4.2 开始,它已被弃用。
以下清单显示了一个示例配置:
XML
<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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
需要注意的是,目标对象(在前面的示例中是 businessObjectTarget)必须是原型。这允许 PoolingTargetSource 实现创建目标的新实例,以根据需要增长池。你可以查看 AbstractPoolingTargetSource 的 JavaDoc 和你希望使用的具体子类的信息,了解其属性。maxSize 是最基本的,总是保证存在。
在这种情况下,myInterceptor 是需要在同一 IoC 上下文中定义的拦截器的名称。然而,你不需要指定拦截器来使用池化。如果你只想要池化,而不需要其他增强,那么就不要设置 interceptorNames 属性。
你可以配置 Spring,使其能够将任何池化对象转换为 org.springframework.aop.target.PoolingConfig 接口,该接口通过引入暴露了关于池配置和当前大小的信息。你需要定义一个类似于以下的增强器:
XML
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>1
2
3
4
2
3
4
这个增强器是通过在 AbstractPoolingTargetSource 类上调用一个便利方法获得的,因此使用了 MethodInvokingFactoryBean。这个增强器的名称(在这里是 poolConfigAdvisor)必须在 ProxyFactoryBean 中暴露池化对象的拦截器名称列表中。
转换定义如下:
Java
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());1
2
2
Note:通常不需要对无状态的服务对象进行池化。我们认为,由于大多数无状态对象本质上是线程安全的,并且如果资源被缓存,实例池化会有问题,所以它不应该是默认的选择。
通过使用自动代理,可以获得更简单的池化。你可以设置任何自动代理创建器使用的 TargetSource 实现。
9.3. 原型目标源
设置一个 “原型” 目标源与设置一个池化 TargetSource 类似。在这种情况下,每次方法调用都会创建目标的新实例。尽管在现代 JVM 中创建新对象的成本并不高,但是连接新对象(满足其 IoC 依赖)的成本可能更高。因此,除非有非常好的理由,否则你不应该使用这种方法。
要做到这一点,你可以按照以下方式修改前面显示的 poolTargetSource 定义(我们也改了名字,以便清楚):
XML
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>1
2
3
2
3
唯一的属性是目标 Bean 的名称。在 TargetSource 的实现中使用了继承,以确保名称的一致性。与池化目标源一样,目标 Bean 必须是原型 Bean 定义。
9.4. ThreadLocal 目标源
如果你需要为每个传入的请求(即每个线程)创建一个对象,那么 ThreadLocal 目标源就很有用。ThreadLocal 的概念提供了一个 JDK 范围(JDK-wide)的设施,可以透明地将资源存储在线程旁边。设置 ThreadLocalTargetSource 的方法几乎与其他类型的目标源的解释相同,如下例所示:
XML
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>1
2
3
2
3
Note:
ThreadLocal实例在多线程和多类加载器环境中使用不当时可能会带来严重的问题(可能导致内存泄漏)。你应该始终考虑将ThreadLocal包装在其他类中,而不是直接使用ThreadLocal本身(除了在包装类中)。此外,你应该始终记住正确地设置和取消设置(后者只涉及到调用ThreadLocal.set(null))线程本地的资源。无论如何都应该取消设置,因为如果不取消设置,可能会导致问题行为。Spring 的ThreadLocal支持为你做了这些,应该始终优先考虑使用ThreadLocal实例,而不是没有其他适当处理代码。
10. 定义新的增强类型
Spring AOP 设计为可扩展的。虽然目前内部使用的是拦截实现策略,但除了环绕增强、前置增强、抛出增强和返回后增强,还可以支持任意的增强类型。
org.springframework.aop.framework.adapter 包是一个 SPI 包,它允许在不改变核心框架的情况下添加对新的自定义增强类型的支持。自定义 Advice 类型的唯一约束是它必须实现 org.aopalliance.aop.Advice 标记接口。
有关更多信息,请参阅 org.springframework.aop.framework.adapter 的 JavaDoc。