40. Classic Spring AOP 用法

在本附录中,我们讨论了 lower-level Spring AOP API 和 Spring 1.2 applications 中使用的 AOP 支持。对于新的 applications,我们建议使用AOP章节中描述的 Spring 2.0 AOP 支持,但在使用现有的 applications 时,或者在阅读书籍和文章时,您可能会遇到 Spring 1.2 样式示例。 Spring 2.0 与 Spring 1.2 完全向后兼容,Spring 2.0 完全支持本附录中描述的所有内容。

_Sp中的 40.1 Pointcut API

让我们来看看 Spring 如何处理关键的切入点概念。

40.1.1 概念

Spring 的切入点 model 可以独立于通知类型启用切入点重用。可以使用相同的切入点来定位不同的建议。

org.springframework.aop.Pointcut接口是中央接口,用于将建议定位到特定的 classes 和方法。完整的界面如下所示:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut接口拆分为两部分允许重用 class 和方法匹配部分,以及 fine-grained 合成操作(例如使用另一个方法匹配器执行“union”)。

ClassFilter接口用于将切入点限制为给定的一组目标 classes。如果matches()方法始终返回 true,则将匹配所有目标 classes:

public interface ClassFilter {

    boolean matches(Class clazz);

}

MethodMatcher接口通常更重要。完整的界面如下所示:

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);

}

matches(Method, Class)方法用于测试此切入点是否将匹配目标 class 上的给定方法。创建 AOP 代理时可以执行此 evaluation,以避免需要对每个方法调用进行测试。如果 2-argument matches 方法返回给定方法的 true,并且 MethodMatcher 的isRuntime()方法返回 true,则将在每次方法调用时调用 3-argument matches 方法。这使得切入点能够在执行目标通知之前立即查看传递给方法调用的 arguments。

大多数 MethodMatchers 都是静态的,这意味着它们的isRuntime()方法返回 false。在这种情况下,永远不会调用 3-argument matches 方法。

如果可能,尝试使切入点成为静态,允许 AOP framework 在创建 AOP 代理时缓存切入点 evaluation 的结果。

40.1.2 关于切入点的操作

Spring 支持切入点操作:尤其是 union 和 intersection。

  • Union 表示切入点匹配的方法。

  • 交叉意味着两个切入点 match 的方法。

  • Union 通常更有用。

  • 可以使用 org.springframework.aop.support.Pointcuts class 中的静态方法或使用同一包中的 ComposablePointcut class 来组合切入点。但是,使用 AspectJ 切入点表达式通常是一种更简单的方法。

40.1.3 AspectJ 表达式切入点

从 2.0 开始,Spring 使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。这是一个切入点,它使用 AspectJ 提供的 library 来解析 AspectJ 切入点表达式 string。

有关受支持的 AspectJ 切入点原语的讨论,请参见上一章。

40.1.4 便利切入点实现

Spring 提供了几个方便的切入点实现。有些可以开箱即用;其他的意图是在 application-specific 切入点中进行子类化。

静态切入点

静态切入点基于方法和目标 class,并且不能考虑方法的 arguments。静态切入点对于大多数用途来说足够 - 而且最好。当首次调用方法时,Spring 可能只评估一次静态切入点:之后,不需要再次使用每个方法调用来评估切入点。

让我们考虑 Spring 中包含的一些静态切入点 implementations。

正则表达式切入点

指定静态切入点的一种显而易见的方法是正则表达式。除了 Spring 之外,几个 AOP 框架使这成为可能。 org.springframework.aop.support.Perl5RegexpMethodPointcut是使用 Perl 5 正则表达式语法的通用正则表达式切入点。 Perl5RegexpMethodPointcut class 依赖于 Jakarta ORO 进行正则表达式匹配。 Spring 还提供JdkRegexpMethodPointcut class,它使用 JDK 1.4 中的正则表达式支持。

使用Perl5RegexpMethodPointcut class,您可以提供 pattern Strings 列表。如果其中任何一个是 match,则切入点将评估为 true。 (所以结果实际上是这些 pointcuts.)的 union

用法如下所示:

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.set.</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring 提供了一个方便 class,RegexpMethodPointcutAdvisor,它允许我们也 reference 一个建议(记住一个建议可以是一个拦截器,在建议之前,抛出建议 etc.)。在幕后,Spring 将使用JdkRegexpMethodPointcut。使用RegexpMethodPointcutAdvisor简化了布线,因为一个 bean 封装了切入点和建议,如下所示:

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

RegexpMethodPointcutAdvisor 可以与任何建议类型一起使用。

Attribute-driven 切入点

一种重要的静态切入点是 metadata-driven 切入点。这使用元数据属性的值:通常是 source-level 元数据。

动态切入点

与静态切入点相比,动态切入点的评估成本更高。它们考虑了方法参数以及静态信息。这意味着必须使用每个方法调用来评估它们;结果无法缓存,因为 arguments 会有所不同。

主要示例是control flow切入点。

控制流量切入点

Spring 控制流切入点在概念上类似于 AspectJ cflow 切入点,虽然功能较弱。 (目前无法指定切入点在另一个 pointcut.)匹配的连接点下执行。控制流切入点与当前调用堆栈匹配。对于 example,如果连接点由com.mycompany.web包中的方法调用,则可能会触发,或SomeCaller class。使用org.springframework.aop.support.ControlFlowPointcut class 指定控制流切入点。

在运行时评估控制流切入点的成本远远高于其他动态切入点。在 Java 1.4 中,成本大约是其他动态切入点的 5 倍。

40.1.5 Pointcut 超类

Spring 提供了有用的切入点超类来帮助您实现自己的切入点。

因为静态切入点最有用,所以您可能会将 StaticMethodMatcherPointcut 子类化,如下所示。这需要实现一个抽象方法(尽管可以覆盖其他方法来自定义行为):

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }

}

还有动态切入点的超类。

您可以在 Spring 1.0 RC2 及更高版本中使用任何建议类型的自定义切入点。

40.1.6 自定义切入点

因为 Spring AOP 中的切入点是 Java classes,而不是语言 features(如在 AspectJ 中),因此可以声明自定义切入点,无论是静态还是动态。 Spring 中的自定义切入点可以是任意复杂的。但是,如果可能,建议使用 AspectJ 切入点表达式语言。

更高版本的 Spring 可能会为 JAC 提供的“语义切入点”提供支持:例如,“所有改变目标 object 中实例变量的方法”。

40.2 Spring 中的建议 API

现在让我们来看看 Spring AOP 如何处理建议。

40.2.1 建议生命周期

每个建议都是 Spring bean。建议实例可以在所有建议的 objects 之间共享,或者对每个建议的 object 都是唯一的。这对应于 per-class 或 per-instance 建议。

Per-class 建议最常用。它适用于 transaction 顾问等通用建议。这些不依赖于代理 object 的 state 或添加新的 state;他们只是按照方法和 arguments 行事。

Per-instance 建议适合介绍,以支持 mixins。在这种情况下,建议将 state 添加到代理的 object。

可以在同一个 AOP 代理中使用共享和 per-instance 建议的混合。

40.2.2 Spring 中的建议类型

Spring 提供了几种开箱即用的建议类型,并且可以扩展以支持任意建议类型。让我们看看基本概念和标准建议类型。

围绕建议拦截

Spring 中最基本的建议类型是围绕建议进行拦截。

Spring 符合 AOP Alliance 接口,可以使用方法拦截来获取建议。实现 around 建议的 MethodInterceptors 应该实现以下接口:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke()方法的MethodInvocation参数公开了被调用的方法;目标连接点; AOP 代理;和方法的 arguments。 invoke()方法应该_return 调用的结果:连接点的 return value。

一个简单的MethodInterceptor implementation 看起来如下:

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;
    }

}

请注意对 MethodInvocation 的proceed()方法的调用。这沿拦截器链向下进入连接点。大多数拦截器将调用此方法,并 return 其 return value。但是,与任何 around 建议一样,MethodInterceptor 可以 return 不同的 value 或抛出 exception 而不是调用 proceed 方法。但是,你没有充分的理由不想这样做!

MethodInterceptors 提供与其他 AOP Alliance-compliant AOP implementations 的互操作性。本节其余部分讨论的其他建议类型实现了 common AOP 概念,但是以 Spring-specific 方式实现。虽然使用最具体的建议类型是有优势的,但如果您可能想要在另一个 AOP framework 中运行 aspect,请坚持使用 MethodInterceptor 建议。请注意,切入点目前在框架之间不可互操作,AOP 联盟目前不定义切入点接口。

在建议之前

更简单的建议类型是之前的建议。这不需要MethodInvocation object,因为只有在进入方法之前才会调用它。

之前建议的主要优点是不需要调用proceed()方法,因此不会无意中无法继续拦截链。

MethodBeforeAdvice界面如下所示。 (Spring 的 API 设计允许在建议之前使用字段,尽管通常的 objects 适用于字段拦截,并且 Spring 不太可能实现它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;

}

注意 return 类型是void。在建议之前,可以在连接点执行之前插入自定义行为,但不能更改 return value。如果 before advice 抛出 exception,这将中止拦截器链的进一步执行。 exception 将传播回拦截器链。如果未选中,或者在被调用方法的签名上,它将直接传递给 client;否则它将被 AOP 代理包含在未经检查的 exception 中。

Spring 中一个 before 建议的示例,它计算所有方法调用:

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;
    }
}

之前建议可以与任何切入点一起使用。

抛出建议

如果连接点抛出 exception,则在连接点的 return 之后调用抛出建议。 Spring 提供类型投掷建议。请注意,这意味着org.springframework.aop.ThrowsAdvice接口不包含任何方法:它是一个标记接口,用于标识给定的 object 实现一个或多个类型化的 throws 建议方法。这些应该是以下形式:

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 arguments,具体取决于 advice 方法是否对方法和 arguments 感兴趣。以下 classes 是 throws 建议的示例。

如果抛出RemoteException(包括子类),则调用以下建议:

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

}

如果抛出ServletException,则调用以下建议。与上面的建议不同,它声明了 4 个 arguments,因此它可以访问被调用的方法,方法 arguments 和 target object:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }

}

最后的 example 说明了如何在单个 class 中使用这两个方法,它们同时处理RemoteExceptionServletException。可以在单个 class 中组合任意数量的 throws 建议方法。

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
    }
}

注意:如果 throws-advice 方法本身抛出 exception,它将覆盖原始的 exception(i.e.更改抛出给用户的 exception)。覆盖的 exception 通常是 RuntimeException;这与任何方法签名兼容。但是,如果 throws-advice 方法抛出已检查的 exception,则必须 match 声明目标方法的声明的 exceptions,因此在某种程度上耦合到特定的目标方法签名。不要抛出与目标方法的签名不兼容的未声明的已检查 exception!

抛出建议可以与任何切入点一起使用。

返回建议后

在 Spring 中返回后的建议必须实现 org.springframework.aop.AfterReturningAdvice 接口,如下所示:

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args,
            Object target) throws Throwable;

}

返回后的建议可以访问 return value(它无法修改),调用方法,方法 arguments 和 target。

返回建议后的以下内容计算所有未抛出 exceptions 的成功方法调用:

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;
    }

}

此建议不会更改执行路径。如果它抛出一个 exception,这将被拦截链而不是 return value 抛出。

返回建议后可以使用任何切入点。

介绍建议

Spring 将介绍建议视为一种特殊的拦截建议。

简介需要IntroductionAdvisorIntroductionInterceptor,实现以下接口:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);

}

从 AOP Alliance MethodInterceptor接口继承的invoke()方法必须实现介绍:即,如果被调用的方法在引入的接口上,则引入拦截器负责处理方法调用 - 它不能调用proceed()

引言建议不能与任何切入点一起使用,因为它仅适用于 class,而不是方法,level。您只能使用IntroductionAdvisor的介绍建议,它具有以下方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;

}

public interface IntroductionInfo {

    Class[] getInterfaces();

}

没有MethodMatcher,因此没有Pointcut与介绍建议相关联。只有 class 过滤是合乎逻辑的。

getInterfaces()方法返回此顾问程序引入的接口。

内部使用validateInterfaces()方法来查看引入的接口是否可以由配置的IntroductionInterceptor实现。

让我们看一下 Spring 测试套件中的一个简单 example。假设我们想要将以下接口引入一个或多个 objects:

public interface Lockable {

    void lock();

    void unlock();

    boolean locked();

}

这说明了一个混合。我们希望能够将建议的 objects 转换为 Lockable,无论其类型如何,并调用锁定和解锁方法。如果我们调用 lock()方法,我们希望所有 setter 方法都抛出LockedException。因此,我们可以添加一个 aspect,它提供了使 objects 不可变的能力,而不需要它们知道它:AOP 的一个很好的例子。

首先,我们需要IntroductionInterceptor来完成繁重的任务。在这种情况下,我们扩展org.springframework.aop.support.DelegatingIntroductionInterceptor便利 class。我们可以直接实现 IntroductionInterceptor,但在大多数情况下使用DelegatingIntroductionInterceptor是最好的。

DelegatingIntroductionInterceptor旨在委托对引入的 interface(s 的实际 implementation 的介绍,隐藏使用拦截这样做.可以使用构造函数参数将委托设置为任何 object;默认委托(当使用 no-arg 构造函数时)就是这个。因此,在下面的 example 中,委托是DelegatingIntroductionInterceptorLockMixin子类。给定一个委托(默认情况下),DelegatingIntroductionInterceptor实例查找委托实现的所有接口(除了 IntroductionInterceptor),并支持对其中任何接口的介绍。子类(如LockMixin)可以调用suppressInterface(Class intf)方法来抑制不应公开的接口。但是,无论IntroductionInterceptor准备支持多少个接口,IntroductionAdvisor都将控制实际公开的接口。引入的接口将隐藏目标对同一接口的任何 implementation。

因此 LockMixin 子类DelegatingIntroductionInterceptor并实现 Lockable 本身。超类自动选择可以支持 Lockable 引入,因此我们不需要指定。我们可以用这种方式引入任意数量的接口。

注意使用locked实例变量。这有效地将额外的 state 添加到目标 object 中的 state。

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);
    }

}

通常没有必要覆盖invoke()方法:DelegatingIntroductionInterceptor implementation - 如果引入方法,则_call 委托方法,否则进入连接点 - 通常就足够了。在本例中,我们需要添加一个检查:如果处于锁定模式,则不能调用 setter 方法。

需要的介绍顾问很简单。它需要做的只是保持一个独特的LockMixin实例,并指定引入的接口 - 在这种情况下,只需Lockable。更复杂的 example 可能会对引入拦截器(它将被定义为原型)进行 reference:在这种情况下,LockMixin没有 configuration 相关,所以我们只需使用new创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }

}

我们可以非常简单地应用这个顾问:它不需要 configuration。 (但是,有必要:在没有 IntroductionAdvisor.)的情况下使用IntroductionInterceptor是不可能像往常一样介绍,顾问必须是 per-instance,因为它是有状态的。我们需要一个不同的LockMixinAdvisor实例,因此LockMixin,对于每个建议的 object。顾问包含建议 object 的 state 的一部分。

我们可以使用Advised.addAdvisor()方法以编程方式应用此顾问程序,或者在 XML configuration 中使用(推荐的方式),就像任何其他顾问程序一样。下面讨论的所有代理创建选项,包括“自动代理创建器”,正确处理引入和有状态混合。

_Sp中的 40.3 Advisor API

在 Spring 中,Advisor 是一个 aspect,它只包含一个与切入点表达式相关联的建议 object。

除了介绍的特殊情况,任何顾问都可以使用任何建议。 org.springframework.aop.support.DefaultPointcutAdvisor是最常用的顾问 class。例如,它可以与MethodInterceptorBeforeAdviceThrowsAdvice一起使用。

可以在同一个 AOP 代理中混合 Spring 中的顾问程序和通知类型。对于 example,您可以在一个代理 configuration 中使用拦截建议,抛出建议和建议之前:Spring 将自动创建必要的拦截器链。

40.4 使用 ProxyFactoryBean 创建 AOP 代理

如果您正在为业务 objects 使用 Spring IoC 容器(ApplicationContext 或 BeanFactory) - 您应该这样做! - 你会想要使用 Spring 的 AOP FactoryBeans 之一。 (请记住,工厂 bean 引入了一个间接层,使其能够创建不同的 type.)的 objects

Spring 2.0 AOP 支持还使用了工厂 beans。

在 Spring 中创建 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean。这样可以完全控制将要应用的切入点和建议以及它们的 ordering。但是,如果您不需要此类控件,则可以使用更简单的选项。

40.4.1 基础知识

与其他 Spring FactoryBean __mplementations 一样,ProxyFactoryBean引入了 level 间接。如果使用 name foo定义ProxyFactoryBean,那么引用foo see 的 objects 不是ProxyFactoryBean实例本身,而是由ProxyFactoryBean's implementation of the getObject()`方法创建的 object。此方法将创建一个包装目标 object 的 AOP 代理。

使用ProxyFactoryBean或另一个 IoC-aware class 创建 AOP 代理的最重要的好处之一是,它意味着建议和切入点也可以由 IoC 管理。这是一个强大的 feature,可以实现其他 AOP 框架难以实现的某些方法。对于 example,一个建议本身可以 reference application objects(除了目标,它应该在任何 AOP framework 中可用),受益于 Dependency Injection 提供的所有可插拔性。

40.4.2 JavaBean properties

在 common 中,Spring 提供了大多数FactoryBean __mplement,ProxyFactoryBean class 本身就是一个 JavaBean。它的 properties 用于:

  • 指定要代理的目标。

  • 指定是否使用 CGLIB(参见下文和第 12.5.3 节,“JDK-和 CGLIB-based 代理”)。

一些 key properties 继承自org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)。这些 key properties 包括:

  • proxyTargetClasstrue如果要代理目标 class,而不是目标 class'接口。如果此 property value 设置为true,则将创建 CGLIB 代理(但也请参见第 12.5.3 节,“JDK-和 CGLIB-based 代理”下方)。

  • optimize:控制是否将积极优化应用于通过 CGLIB 创建的代理。除非完全理解相关 AOP 代理如何处理优化,否则不应轻易使用此设置。目前仅用于 CGLIB 代理;它对 JDK 动态代理没有影响。

  • frozen:如果代理 configuration 为frozen,则不再允许更改为 configuration。这既可以作为轻微优化,也可以用于在创建代理后不希望调用者能够操作代理(通过Advised接口)的情况。此 property 的默认 value 为false,因此允许添加其他建议等更改。

  • exposeProxy:确定当前代理是否应该在ThreadLocal中公开,以便目标可以访问它。如果目标需要获取代理并且exposeProxy property 设置为true,则目标可以使用AopContext.currentProxy()方法。

  • aopProxyFactory:要使用的AopProxyFactory的 implementation。提供一种自定义是否使用动态代理,CGLIB 或任何其他代理策略的方法。默认的 implementation 将适当地选择动态代理或 CGLIB。应该没有必要使用这个 property;它旨在允许在 Spring 1.1 中添加新的代理类型。

特定于ProxyFactoryBean的其他 properties 包括:

  • proxyInterfaces:String 接口名称的 array。如果未提供,则将使用目标 class 的 CGLIB 代理(但也请参见第 12.5.3 节,“JDK-和 CGLIB-based 代理”下方)。

  • interceptorNames:String array of Advisor,拦截器或其他要应用的建议名称。 Ordering 很重要,在第一个 come-first 服务的基础上。也就是说,列表中的第一个拦截器将是第一个能够拦截调用的拦截器。

名称是当前工厂中的 bean 名称,包括来自祖先工厂的 bean 名称。你不能在这里提到 bean references,因为这样做会导致ProxyFactoryBean忽略建议的 singleton 设置。

您可以使用星号(*)附加拦截器 name。这将导致应用所有顾问 beans,其名称以要应用星号之前的部分开头。使用此 feature 的示例可以在第 12.5.6 节,“使用'global'顾问”中找到。

  • singleton:无论调用getObject()方法的频率如何,工厂是否应该 return 一个 object。几个FactoryBean implementations 提供了这样一种方法。默认的 value 是true。如果你想使用有状态的建议 - 对于 example,对于有状态的 mixins - 使用原型建议和false的 singleton value。

40.4.3 JDK-和 CGLIB-based 代理

本节作为关于ProxyFactoryBean如何选择为特定目标 object(即代理)创建 JDK 和 CGLIB-based 代理之一的权威文档。

关于创建 JDK-或 CGLIB-based 代理的行为在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。对于 auto-detecting 接口,ProxyFactoryBean现在表现出与TransactionProxyFactoryBean class 相似的语义。

如果要代理的目标 object 的 class(以下简称为 target class)未实现任何接口,则将创建 CGLIB-based 代理。这是最简单的方案,因为 JDK 代理是基于接口的,没有接口意味着甚至不可能进行 JDK 代理。只需插入目标 bean,并通过interceptorNames property 指定拦截器列表。请注意,即使ProxyFactoryBeanproxyTargetClass property 已设置为false,也会创建 CGLIB-based 代理。 (显然这没有意义,最好从 bean 定义中删除,因为它最多是多余的,最坏的 confusing.)

如果目标 class 实现一个(或多个)接口,则创建的代理类型取决于ProxyFactoryBean的 configuration。

如果ProxyFactoryBeanproxyTargetClass property 已设置为true,则将创建 CGLIB-based 代理。这是有道理的,并且符合最少惊喜的原则。即使ProxyFactoryBeanproxyInterfaces property 已设置为一个或多个完全限定的接口名称,proxyTargetClass property 设置为true这一事实也会导致 CGLIB-based 代理生效。

如果ProxyFactoryBeanproxyInterfaces property 已设置为一个或多个完全限定的接口名称,则将创建 JDK-based 代理。创建的代理将实现proxyInterfaces property 中指定的所有接口;如果目标 class 碰巧实现了比proxyInterfaces property 中指定的接口更多的接口,那么这一切都很好,但返回的代理将不会实现这些额外的接口。

如果没有设置ProxyFactoryBeanproxyInterfaces property,但是目标 class 确实实现了一个(或多个)接口,那么ProxyFactoryBean将目标 class 确实实现至少一个接口的事实,并且 JDK-based 代理将是创建。实际代理的接口将是 target class 实现的所有接口;实际上,这与仅提供目标 class 为proxyInterfaces property 实现的每个接口的列表相同。但是,它的工作量明显减少,并且不太容易出现错别字。

40.4.4 代理接口

让我们看一下ProxyFactoryBean的简单示例。这个例子涉及:

  • 将被代理的目标 bean。这是下面 example 中的“personTarget”bean 定义。

  • 用于提供建议的顾问和拦截器。

  • AOP 代理 bean 定义,指定目标 object(personTarget bean)和要代理的接口,以及要应用的建议。

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name"><value>Tony</value></property>
    <property name="age"><value>51</value></property>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property value</value></property>
</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</value></property>
    <property name="target"><ref bean="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

请注意,interceptorNames property 采用 String 列表:当前工厂中拦截器或顾问程序的 bean 名称。顾问,拦截器,返回之前,之后和投掷建议 objects 都可以使用。顾问的排序非常重要。

您可能想知道为什么列表不包含 bean references。原因是如果 ProxyFactoryBean 的 singleton property 设置为 false,它必须能够 return 独立的代理实例。如果任何顾问本身就是原型,则需要返回一个独立的实例,因此必须能够从工厂获得原型的实例;持有参考是不够的。

上面的“person”bean 定义可用于代替 Person implementation,如下所示:

Person person = (Person) factory.getBean("person");

同一个 IoC context 中的其他 beans 可以表达对它的强类型依赖,就像普通的 Java object 一样:

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person" /></property>
</bean>

此 example 中的PersonUser class 将显示 Person 类型的 property。就其而言,AOP 代理可以透明地用于代替“真正的”人 implementation。但是,它的 class 将是一个动态代理 class。可以将它转换为Advised接口(下面讨论)。

可以使用匿名内部 bean 隐藏目标和代理之间的区别,如下所示。只有ProxyFactoryBean定义不同;仅包含完整性的建议:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property value</value></property>
</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</value></property>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name"><value>Tony</value></property>
            <property name="age"><value>51</value></property>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

这样做的好处是只有一个Person类型的 object:如果我们想要阻止 application context 的用户获取 un-advised object 的 reference,或者需要避免 Spring IoC 自动装配的任何歧义,这将非常有用。 ProxyFactoryBean 定义为 self-contained 也有一个优势。但是,有时能够从工厂获得 un-advised 目标实际上可能是一个优势:例如,在某些测试场景中。

40.4.5 代理 classes

如果您需要代理 class 而不是一个或多个接口,该怎么办?

想象一下,在上面的例子中,没有Person接口:我们需要建议一个没有实现任何业务接口的 class Person。在这种情况下,您可以将 Spring 配置为使用 CGLIB 代理,而不是动态代理。只需将上面的 ProxyFactoryBean 上的proxyTargetClass property 设置为 true 即可。虽然最好编写接口而不是 classes,但在使用 legacy code 时,建议不实现接口的 classes 的功能非常有用。 (一般来说,Spring 不是规定性的.虽然它可以很容易地应用好的做法,但它避免强迫特定的 approach.)

如果您愿意,即使您有接口,也可以在任何情况下强制使用 CGLIB。

CGLIB 代理通过在运行时生成目标 class 的子类来工作。 Spring 将此生成的子类配置为将方法 calls 委托给原始目标:子类用于实现 Decorator pattern,在通知中编织。

CGLIB 代理通常应对用户透明。但是,有一些问题需要考虑:

  • 无法建议Final方法,因为它们无法被覆盖。

  • 从 Spring 3.2 开始,不再需要将 CGLIB 添加到项目 classpath 中。 CGLIB classes 已在 org.springframework 下重新打包,并直接包含在 spring-core JAR 中。这既是为了方便用户,也是为了避免与依赖于 CGLIB 的不同版本的其他项目的潜在冲突。

CGLIB 代理和动态代理之间的性能差异很小。从 Spring 1.0 开始,动态代理略快一些。但是,这可能会在未来发生变化。 在这种情况下,绩效不应该是一个决定性的考虑因素。

40.4.6 使用'global'顾问

通过在拦截器 name 上附加星号,所有 bean 名称与星号前面的部分匹配的顾问程序将被添加到顾问程序链中。如果您需要添加一组标准的'global'顾问程序,这可以派上用场:

<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"/>

40.5 简明的代理定义

特别是在定义 transactional 代理时,最终可能会有许多类似的代理定义。使用 parent 和 child bean 定义以及内部 bean 定义可以产生更清晰,更简洁的代理定义。

首先为代理创建 parent,template,bean 定义:

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

这永远不会被实例化,所以可能实际上是不完整的。然后,每个需要创建的代理只是一个 child bean 定义,它将代理的目标包装为内部 bean 定义,因为目标永远不会单独使用。

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

当然可以从 parent 模板覆盖 properties,例如在这种情况下,transaction 传播设置:

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

请注意,在上面的 example 中,我们使用 abstract 属性显式地将 parent bean 定义标记为 abstract,如先前所述,因此实际上可能无法实例化它。 Application contexts(但不是简单的 bean 工厂)将默认为 pre-instantiate 所有单例。因此,重要的是(至少对于 singleton beans),如果你有一个(parent)bean 定义,你打算只用作模板,并且这个定义指定了一个 class,你必须确保将该抽象属性设置为 true,否则 application context 实际上会尝试 pre-instantiate 它。

40.6 使用 ProxyFactory 以编程方式创建 AOP 代理

使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以使用 Spring AOP 而不依赖于 Spring IoC。

以下清单显示了为目标 object 创建代理,其中包含一个拦截器和一个顾问程序。 target object 实现的接口将自动代理:

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addInterceptor(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

第一个 step 是构造一个org.springframework.aop.framework.ProxyFactory类型的 object。您可以使用目标 object 创建它,如上面的 example,或者指定要在备用构造函数中代理的接口。

您可以添加拦截器或顾问程序,并在 ProxyFactory 的生命周期中对其进行操作。如果添加 IntroductionInterceptionAroundAdvisor,则可以使代理实现其他接口。

ProxyFactory 上还有一些便捷方法(继承自AdvisedSupport),它允许您添加其他建议类型,例如 before 和 throws 建议。 AdvisedSupport 是 ProxyFactory 和 ProxyFactoryBean 的超类。

在大多数应用程序中,将 AOP 代理创建与 IoC framework 集成是最佳实践。我们建议您通常使用 AOP 从 Java code 外部化 configuration。

40.7 操纵建议 objects

但是,您创建 AOP 代理,可以使用org.springframework.aop.framework.Advised接口操作它们。任何 AOP 代理都可以转换为此接口,无论它实现哪个其他接口。该界面包括以下方法:

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();

getAdvisors()方法将为每个顾问,拦截器或其他已添加到工厂的建议类型返回一个顾问。如果您添加了 Advisor,则此索引处返回的顾问程序将是您添加的 object。如果您添加了一个拦截器或其他建议类型,Spring 将把它包装在一个顾问程序中,并且切入点总是返回 true。因此,如果添加了MethodInterceptor,则为此索引返回的顾问程序将是DefaultPointcutAdvisor返回MethodInterceptor以及与所有 classes 和方法匹配的切入点。

addAdvisor()方法可用于添加任何 Advisor。通常,持有切入点和建议的顾问将是通用DefaultPointcutAdvisor,它可以与任何建议或切入点一起使用(但不适用于介绍)。

默认情况下,即使创建了代理,也可以添加或删除顾问程序或拦截器。唯一的限制是无法添加或删除介绍顾问,因为工厂的现有代理不会显示接口更改。 (您可以从工厂获取新代理以避免此 problem.)

将 AOP 代理转换为Advised接口并检查和操作其建议的简单示例:

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

尽管毫无疑问是合法的使用案例,否则修改 production 中商业 object 的建议是否明智(没有双关语)是值得怀疑的。但是,它在开发中非常有用:例如,在测试中。我有时发现能够以拦截器或其他建议的形式添加 test code 非常有用,进入我想要测试的方法调用。 (例如,建议可以进入为该方法创建的 transaction:对于 example,要 run SQL 检查数据库是否已正确更新,然后标记 transaction for roll back.)

根据您创建代理的方式,通常可以设置frozen flag,在这种情况下Advised isFrozen()方法将 return true,并且任何通过添加或删除修改建议的尝试都将导致AopConfigException。在某些情况下,冻结建议的 object 的 state 的功能非常有用,例如,为了防止调用 code 删除安全拦截器。如果已知不需要运行时建议修改,它也可以在 Spring 1.1 中使用以允许积极优化。

40.8 使用“autoproxy”工具

到目前为止,我们已经考虑使用ProxyFactoryBean或类似的工厂 bean 显式创建 AOP 代理。

Spring 还允许我们使用“autoproxy”bean 定义,它可以自动代理所选的 bean 定义。这是基于 Spring“bean 后处理器”基础结构构建的,它可以在容器加载时修改任何 bean 定义。

在此 model 中,您在 XML bean 定义文件中设置了一些特殊的 bean 定义来配置自动代理基础结构。这允许您只声明符合 autoproxying 的目标:您不需要使用ProxyFactoryBean

有两种方法可以做到这一点:

  • 使用 autoproxy 创建器引用当前 context 中的特定 beans。

  • autoproxy 创建的一个特例,值得单独考虑;由 source-level 元数据属性驱动的 autoproxy 创建。

40.8.1 Autoproxy bean 定义

org.springframework.aop.framework.autoproxy包提供以下标准 autoproxy 创建者。

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator class 是BeanPostProcessor,它自动为 beans 创建名称与文字值或通配符匹配的 AOP 代理。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames"><value>jdk*,onlyJdk</value></property>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

ProxyFactoryBean一样,有一个interceptorNames property 而不是拦截器列表,以允许原型顾问的正确行为。命名为“拦截器”可以是顾问或任何建议类型。

与一般的自动代理一样,使用BeanNameAutoProxyCreator的主要目的是将相同的 configuration 一致地应用于多个 objects,并使用最少的 configuration 卷。将声明性 transactions 应用于多个 objects 是一种流行的选择。

Bean 定义的名称 match,例如上面的 example 中的“jdkMyBean”和“onlyJdk”,是带有目标 class 的普通旧 bean 定义。 BeanNameAutoProxyCreator将自动创建 AOP 代理。相同的建议将应用于所有匹配的 beans。请注意,如果使用顾问程序(而不是上面的 example 中的拦截器),则切入点可能会以不同的方式应用于不同的 beans。

DefaultAdvisorAutoProxyCreator

更通用且功能更强大的自动代理创建者是DefaultAdvisorAutoProxyCreator。这将在当前 context 中自动应用符合条件的顾问程序,而无需在 autoproxy advisor 的 bean 定义中包含特定的 bean 名称。它提供了与BeanNameAutoProxyCreator一致的 configuration 和避免重复的相同优点。

使用此机制涉及:

  • 指定DefaultAdvisorAutoProxyCreator bean 定义。

  • 在相同或相关的上下文中指定任意数量的顾问。请注意,这些必须是顾问,而不仅仅是拦截器或其他建议。这是必要的,因为必须有一个切入点来评估,以检查每个建议是否符合候选 bean 定义的资格。

DefaultAdvisorAutoProxyCreator将自动评估每个顾问程序中包含的切入点,以查看它应该应用于每个业务 object 的建议(如果有的话)(例如 example 中的“businessObject1”和“businessObject2”)。

这意味着可以自动将任意数量的顾问程序应用于每个业务 object。如果任何顾问程序中的切入点都不匹配业务 object 中的任何方法,则不会代理 object。当为新业务 objects 添加 bean 定义时,如有必要,它们将自动被代理。

通常,自动代理具有使调用者或依赖项无法获得 un-advised object 的优点。在此 ApplicationContext 上调用 getBean(“businessObject1”)将_return AOP 代理,而不是目标业务 object。 (前面显示的“内部 bean”习语也提供了这个 benefit.)

<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"/>

如果要将相同的建议一致地应用于许多业务 objects,则DefaultAdvisorAutoProxyCreator非常有用。基础结构定义到位后,您只需添加新的业务 objects,而无需包含特定的代理 configuration。您还可以非常轻松地使用其他方面 - 例如,跟踪或 performance 监视方面 - 只需对 configuration 进行最小的更改。

DefaultAdvisorAutoProxyCreator 支持过滤(使用命名约定,以便仅评估某些顾问程序,允许在同一工厂中使用多个不同配置的 AdvisorAutoProxyCreators)和 ordering。如果这是一个问题,顾问可以实现org.springframework.core.Ordered接口以确保正确的 ordering。上面的 example 中使用的 TransactionAttributeSourceAdvisor 具有可配置的 order value;默认设置是无序的。

AbstractAdvisorAutoProxyCreator

这是 DefaultAdvisorAutoProxyCreator 的超类。你可以通过继承这个 class 来创建你自己的 autoproxy 创建者,在不太可能的 event 中,顾问定义对 framework DefaultAdvisorAutoProxyCreator的行为提供的定制不足。

40.8.2 使用 metadata-driven auto-proxying

一种特别重要的自动代理类型由元数据驱动。这会产生类似的编程 model 到.NET ServicedComponents。不像在 EJB 中那样使用 XML 部署描述符,transaction management 和其他企业服务的 configuration 保存在 source-level 属性中。

在这种情况下,您将DefaultAdvisorAutoProxyCreator与理解元数据属性的 Advisors 结合使用。元数据细节保存在候选顾问的切入点部分中,而不是在 autoproxy 创建 class 本身中。

这实际上是DefaultAdvisorAutoProxyCreator的一个特例,但值得自己考虑。 (metadata-aware code 位于顾问程序中包含的切入点,而不是 AOP framework itself.)

JPetStore sample application 的/attributes目录显示了 attribute-driven autoproxying 的使用。在这种情况下,不需要使用TransactionProxyFactoryBean。简单地在 business objects 上定义 transactional 属性就足够了,因为使用了 metadata-aware 切入点。 bean 定义在/WEB-INF/declarativeServices.xml中包含以下 code。请注意,这是通用的,可以在 JPetStore 外部使用:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
            <property name="attributes" ref="attributes"/>
        </bean>
    </property>
</bean>

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

DefaultAdvisorAutoProxyCreator bean 定义(name 不重要,因此甚至可以省略)将获取当前 application context 中所有符合条件的切入点。在这种情况下,类型为TransactionAttributeSourceAdvisor的“transactionAdvisor”bean 定义将应用于带有 transaction 属性的 classes 或方法。 TransactionAttributeSourceAdvisor 依赖于 TransactionInterceptor,通过构造函数依赖。 example 通过自动装配解决了这个问题。 AttributesTransactionAttributeSource取决于org.springframework.metadata.Attributes接口的 implementation。在这个片段中,“attributes”bean 满足这一点,使用 Jakarta Commons _Attributes API 获取属性信息。 (application code 必须使用 Commons _Attributes 编译 task.)编译

JPetStore sample application 的/annotation目录包含由 JDK 1.5 annotations 驱动的 auto-proxying 的类似 example。以下 configuration 可以自动检测 Spring 的Transactional annotation,从而导致包含该注释的 beans 的隐式代理:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
    </property>
</bean>

这里定义的TransactionInterceptor取决于PlatformTransactionManager定义,该定义不包含在此通用文件中(尽管它可能是),因为它将特定于 application 的 transaction 要求(通常是 JTA,如_示例,或 Hibernate,JDO 或 JDBC) :

<bean id="transactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager"/>

如果只需要声明性 transaction management,那么使用这些通用 XML 定义将导致 Spring 自动使用 transaction 属性代理所有 classes 或方法。您不需要直接使用 AOP,编程 model 类似于.NET ServicedComponents。

这种机制是可扩展的。可以根据自定义属性进行自动代理。你需要:

  • 定义自定义属性。

  • 指定具有必要建议的 Advisor,包括由 class 或方法上存在自定义属性触发的切入点。您可以使用现有建议,只需实现一个拾取自定义属性的静态切入点。

这些顾问对于每个建议的 class(对于 example,mixins)来说都是唯一的:它们只需要被定义为原型,而不是 singleton,bean 定义。例如,如上所示,来自 Spring 测试套件的LockMixin介绍拦截器可以与 attribute-driven 切入点一起使用以定位 mixin,如此处所示。我们使用通用DefaultPointcutAdvisor,使用 JavaBean properties 配置:

<bean id="lockMixin" class="org.springframework.aop.LockMixin"
        scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        scope="prototype">
    <property name="pointcut" ref="myAttributeAwarePointcut"/>
    <property name="advice" ref="lockMixin"/>
</bean>

<bean id="anyBean" class="anyclass" ...

如果属性识别切入点匹配anyBean或其他 bean 定义中的任何方法,则将应用 mixin。请注意,lockMixinlockableAdvisor定义都是原型。 myAttributeAwarePointcut切入点可以是 singleton 定义,因为它不为个别建议的 objects 保留 state。

40.9 使用 TargetSources

Spring 提供了 TargetSource 的概念,以org.springframework.aop.TargetSource接口表示。该接口负责返回实现连接点的“target object”。在 AOP 代理处理方法调用的每个 time 时,都要求TargetSource implementation 提供目标实例。

使用 Spring AOP 的开发人员通常不需要直接使用 TargetSources,但这提供了支持池,热插拔和其他复杂目标的强大方法。对于 example,池化 TargetSource 可以为每次调用返回一个不同的目标实例,使用池来管理实例。

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

让我们看一下 Spring 提供的标准目标源,以及如何使用它们。

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

40.9.1 热插拔目标源

org.springframework.aop.target.HotSwappableTargetSource的存在是为了允许切换 AOP 代理的目标,同时允许调用者保持对它的 references。

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

您可以通过 HotSwappableTargetSource 上的swap()方法更改目标,如下所示:

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object 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 持有 reference 的客户端将不知道该更改,但会立即开始命中新目标。

虽然这个例子没有添加任何建议 - 并且没有必要添加建议来使用TargetSource - 当然任何TargetSource都可以与任意建议一起使用。

40.9.2 汇集目标来源

使用池化目标源为 stateless session EJB 提供了类似的编程 model,其中维护了相同实例的池,方法调用将在池中释放 objects。

Spring 池和 SLSB 池之间的一个重要区别是 Spring 池可以应用于任何 POJO。与 Spring 一样,此服务可以 non-invasive 方式应用。

Spring 为 Commons _Pool 2.2 提供 out-of-the-box 支持,它提供了一个相当有效的池 implementation。您需要 application 的 classpath 上的 commons-pool Jar 才能使用此 feature。也可以将org.springframework.aop.target.AbstractPoolingTargetSource子类化为支持任何其他池 API。

Commons _Pool 1.5 也受支持,但从 Spring Framework 4.2 开始不推荐使用。

Sample 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>

请注意,example 中的目标 object - “businessObjectTarget”必须是原型。这允许PoolingTargetSource implementation 创建目标的新实例以根据需要增长池。请参阅 Javadoc for AbstractPoolingTargetSource以及您希望用于获取有关其 properties 的信息的具体子类:“maxSize”是最基本的,并且始终保证存在。

在这种情况下,“myInterceptor”是需要在同一 IoC context 中定义的拦截器的 name。但是,没有必要指定拦截器来使用池。如果您只想要池化,而没有其他建议,请不要设置 interceptorNames property。

可以配置 Spring 以便能够将任何池化的 object 转换为org.springframework.aop.target.PoolingConfig接口,该接口通过介绍公开有关 configuration 和池当前大小的信息。你需要定义一个像这样的顾问:

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

这个顾问程序是通过在AbstractPoolingTargetSource class 上调用一个方法获得的,因此使用了 MethodInvokingFactoryBean。此顾问程序的 name(此处为“poolConfigAdvisor”)必须位于 ProxyFactoryBean 中的拦截器名称列表中,以显示池化的 object。

演员表如下:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

通常不需要池化 stateless 服务 objects。我们不相信它应该是默认选择,因为大多数 stateless objects 自然是线程安全的,并且如果资源被缓存,实例池是有问题的。

使用 autoproxying 可以实现更简单的池化。可以设置任何 autoproxy 创建者使用的 TargetSource。

40.9.3 原型目标来源

设置“原型”目标源类似于池化 TargetSource。在这种情况下,将在每个方法调用上创建目标的新实例。虽然在现代 JVM 中创建一个新的 object 的成本并不高,但连接新的 object(满足其 IoC 依赖性)的成本可能更高。因此,如果没有充分理由,就不应该使用这种方法。

为此,您可以按如下方式修改上面显示的poolTargetSource定义。 (我也更改了 name,因为 clarity.)

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

只有一个 property:目标 bean 的 name。 TargetSource implementations 中使用继承来确保一致的命名。与池化目标源一样,目标 bean 必须是原型 bean 定义。

40.9.4 ThreadLocal 目标源

如果需要为每个传入请求创建 object(每个线程),ThreadLocal目标源很有用。 ThreadLocal的概念提供了一个 JDK-wide 工具来透明地与一个线程一起存储资源。设置ThreadLocalTargetSource与其他类型的目标源所解释的几乎相同:

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

ThreadLocals 在 multi-threaded 和 multi-classloader 环境中错误地使用它们时会遇到严重问题(可能导致 memory 泄漏)。应该总是考虑将 threadlocal 包装在其他 class 中,并且永远不要直接使用ThreadLocal本身(当然在 wrapper class 中除外)。此外,应始终记住正确设置和取消设置(后者仅涉及对ThreadLocal.set(null)的调用)线程的本地资源。在任何情况下都应该进行取消设置,因为不取消设置可能会导致有问题的行为。 Spring 的 ThreadLocal 支持为您完成此操作,应始终考虑使用 ThreadLocals 而不使用其他正确的处理 code。

40.10 定义新的建议类型

Spring AOP 旨在可扩展。虽然拦截 implementation 策略目前在内部使用,但除了 out-of-the-box 拦截建议之外,还可以支持任意建议类型,之前,抛出建议和返回建议之后。

org.springframework.aop.framework.adapter包是一个 SPI 包,允许在不更改核心 framework 的情况下添加对新自定义通知类型的支持。自定义Advice类型的唯一约束是它必须实现org.aopalliance.aop.Advice标记接口。

有关详细信息,请参阅org.springframework.aop.framework.adapter包的 Javadocs。

40.11 更多资源

有关 Spring AOP 的更多示例,请参阅 Spring sample applications:

  • JPetStore 的默认 configuration 说明了使用TransactionProxyFactoryBean进行声明式 transaction management。

  • JPetStore 的/attributes目录说明了 attribute-driven declarative transaction management 的使用。