40. 经典 Spring AOP 用法

在本附录中,我们讨论较低级别的 Spring AOP API 和 Spring 1.2 应用程序中使用的 AOP 支持。对于新应用程序,我们建议使用AOP章节中介绍的 Spring 2.0 AOP 支持,但是在使用现有应用程序或阅读书籍和文章时,您可能会遇到 Spring 1.2 样式示例。 Spring 2.0 与 Spring 1.2 完全向后兼容,Spring 2.0 完全支持本附录中描述的所有内容。

Spring 40.1 Pointcut API

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

40.1.1 Concepts

Spring 的切入点模型使切入点重用不受建议类型的影响。使用相同的切入点可以针对不同的建议。

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

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut接口分为两部分,可以重用类和方法匹配的部分,以及细粒度的合成操作(例如与另一个方法匹配器执行“联合”)。

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

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)方法用于测试此切入点是否将与目标类上的给定方法匹配。可以在创建 AOP 代理时执行此评估,以避免需要对每个方法调用进行测试。如果 2 参数匹配方法对给定方法返回 true,而 MethodMatcher 的isRuntime()方法返回 true,则每次调用方法时将调用 3 参数 match 方法。这使切入点可以在执行目标建议之前立即查看传递给方法调用的参数。

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

Tip

如果可能,请尝试使切入点成为静态,从而在创建 AOP 代理时允许 AOP 框架缓存切入点评估的结果。

40.1.2 切入点的操作

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

  • 联合表示两个切入点都匹配的方法。

  • 交集是指两个切入点都匹配的方法。

  • 联合通常更有用。

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

40.1.3 AspectJ 表达式切入点

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

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

40.1.4 便捷切入点实现

Spring 提供了几种方便的切入点实现。有些可以直接使用。其他的则打算在特定于应用程序的切入点中子类化。

Static pointcuts

静态切入点基于方法和目标类,并且不能考虑方法的参数。静态切入点已足够-最好-对于大多数用法。首次调用方法时,Spring 可能只评估一次静态切入点:此后,无需在每次方法调用时再次评估切入点。

让我们考虑一下 Spring 附带的一些静态切入点实现。

正则表达式切入点

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

使用Perl5RegexpMethodPointcut类,可以提供模式字符串的列表。如果其中任何一个匹配,则切入点将评估为 true。 (因此,结果实际上是这些切入点的并集.)

用法如下所示:

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

Spring 提供了一个便利类RegexpMethodPointcutAdvisor,该类使我们也可以引用一个 Advice(请记住,Advice 可以是拦截器,在建议,引发建议等之前)。在后台,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 pointcuts

静态切入点的一种重要类型是元数据驱动的切入点。这使用元数据属性的值:通常是源级别的元数据。

Dynamic pointcuts

动态切入点比静态切入点更昂贵。它们考虑了方法* arguments *以及静态信息。这意味着必须在每次方法调用时对它们进行评估。由于参数会有所不同,因此无法缓存结果。

主要示例是control flow切入点。

控制流切入点

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

Note

与其他动态切入点相比,控制流切入点在运行时进行评估要昂贵得多。在 Java 1.4 中,成本约为其他动态切入点的 5 倍。

40.1.5 切入点超类

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 类,而不是语言功能(如 AspectJ),所以可以声明自定义切入点,无论是静态的还是动态的。 Spring 中的自定义切入点可以任意复杂。但是,如果可能,建议使用 AspectJ 切入点表达语言。

Note

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

Spring 40.2 咨询 API

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

40.2.1 建议生命周期

每个建议都是一个 Spring bean。建议实例可以在所有建议对象之间共享,或者对于每个建议对象都是唯一的。这对应于“每个类别”或“每个实例”建议。

每班建议最常用。适用于一般建议,例如 Transaction 顾问。这些不依赖于代理对象的状态或添加新状态。它们仅作用于方法和参数。

每个实例的建议都适合引入,以支持混合。在这种情况下,建议将状态添加到代理对象。

可以在同一 AOP 代理中混合使用共享和基于实例的建议。

40.2.2 Spring 的建议类型

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

围绕建议进行拦截

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

Spring 与 AOP Alliance 界面兼容,可使用方法拦截获得周围建议。实现围绕建议的 MethodInterceptor 应该实现以下接口:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke()方法的MethodInvocation参数公开了被调用的方法;目标连接点; AOP 代理;以及方法的参数。 invoke()方法应返回调用的结果:连接点的返回值。

一个简单的MethodInterceptor实现如下所示:

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()方法的调用。这沿着拦截器链向下到达连接点。大多数拦截器将调用此方法,并返回其返回值。但是,MethodInterceptor 像任何周围的建议一样,可以返回不同的值或引发异常,而不是调用 proceed 方法。但是,您没有充分的理由就不想这样做!

Note

MethodInterceptors 提供与其他符合 AOP Alliance 的 AOP 实现的互操作性。本节其余部分讨论的其他建议类型将实现常见的 AOP 概念,但以特定于 Spring 的方式。尽管使用最具体的建议类型有一个优势,但是如果您可能想在另一个 AOP 框架中运行方面,则在建议周围使用 MethodInterceptor。请注意,切入点当前无法在框架之间互操作,并且 AOP Alliance 当前未定义切入点接口。

Before advice

一个更简单的建议类型是* before advisor *。不需要MethodInvocation对象,因为它将仅在进入方法之前被调用。

先行建议的主要优点是无需调用proceed()方法,因此不会无意中导致无法沿拦截器链 continue 进行。

MethodBeforeAdvice界面如下所示。 (尽管通常的对象适用于字段侦听,并且 Spring 不太可能实现它,但 Spring 的 API 设计允许先于字段就建议)。

public interface MethodBeforeAdvice extends BeforeAdvice {

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

}

请注意,返回类型为void。通知可以在联接点执行之前插入自定义行为,但不能更改返回值。如果之前的建议引发异常,则会中止拦截器链的进一步执行。异常将传播回拦截链。如果未选中它,或者在被调用方法的签名上,它将被直接传递给 Client 端。否则它将被 AOP 代理包装在未经检查的异常中。

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

Tip

在将建议与任何切入点一起使用之前。

Throws advice

如果联接点抛出异常,则在联接点返回之后调用* throwsadvice *。 Spring 提供类型化的抛出建议。请注意,这意味着org.springframework.aop.ThrowsAdvice接口不包含任何方法:这是一个标记接口,用于标识给定对象实现一个或多个类型的 throws 建议方法。这些形式应为:

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

仅最后一个参数是必需的。方法签名可以具有一个或四个参数,具体取决于建议方法是否对该方法和参数感兴趣。以下类是抛出建议的示例。

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

public class RemoteThrowsAdvice implements ThrowsAdvice {

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

}

如果抛出ServletException,则调用以下建议。与上述建议不同,它声明了 4 个参数,因此可以访问被调用的方法,方法参数和目标对象:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

}

最后一个示例说明了如何在处理RemoteExceptionServletException的单个类中使用这两种方法。可以将任意数量的引发建议方法组合到一个类中。

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 方法本身会引发异常,它将覆盖原始异常(即,将引发的异常更改给用户)。覆盖的异常通常是 RuntimeException;这与任何方法签名都兼容。但是,如果 throws-advice 方法抛出一个已检查的异常,则它必须与目标方法的已声明异常匹配,因此在某种程度上与特定的目标方法签名关联。 请勿抛出与目标方法签名不兼容的未声明检查异常!

Tip

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

返回建议后

在 Spring 中,After After 建议必须实现* org.springframework.aop.AfterReturningAdvice *接口,如下所示:

public interface AfterReturningAdvice extends Advice {

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

}

After After Returning 建议可以访问返回值(无法修改),调用的方法,方法参数和目标。

返回建议后的以下内容将计数所有未引发异常的成功方法调用:

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

}

该建议不会更改执行路径。如果抛出异常,它将被抛出拦截器链,而不是返回值。

Tip

返回后,建议可以与任何切入点一起使用。

Introduction advice

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

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

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);

}

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

简介建议不能与任何切入点一起使用,因为它仅适用于类而不是方法级别。您只能通过IntroductionAdvisor使用介绍建议,该建议具有以下方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;

}

public interface IntroductionInfo {

    Class[] getInterfaces();

}

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

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

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

让我们看一下 Spring 测试套件中的一个简单示例。假设我们要为一个或多个对象引入以下接口:

public interface Lockable {

    void lock();

    void unlock();

    boolean locked();

}

这说明了* mixin *。我们希望能够将建议对象投射到 Lockable,无论它们的类型如何,并调用 lock 和 unlock 方法。如果我们调用 lock()方法,我们希望所有的 setter 方法都抛出LockedException。因此,我们可以增加一个方面,使对象不可变,而对象却不了解:AOP 的一个很好的例子。

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

DelegatingIntroductionInterceptor的设计宗旨是为引入的接口的实际实现委派一个简介,从而隐瞒使用拦截的方式。可以使用构造函数参数将委托设置为任何对象。默认委托(使用 no-arg 构造函数时)是这个。因此,在下面的示例中,委托是DelegatingIntroductionInterceptorLockMixin子类。给定一个委托(默认情况下为本身),一个DelegatingIntroductionInterceptor实例将查找由该委托实现的所有接口(IntroductionInterceptor 除外),并将支持针对其中任何一个的介绍。诸如LockMixin之类的子类可能调用suppressInterface(Class intf)方法来抑制不应公开的接口。但是,无论IntroductionInterceptor准备支持多少个接口,使用的IntroductionAdvisor都将控制实际公开哪些接口。引入的接口将隐藏目标对同一接口的任何实现。

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

请注意locked实例变量的使用。这有效地将附加状态添加到目标对象中保存的状态。

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实现-如果引入了该方法,则调用委托方法,否则进行连接点连接-通常就足够了。在当前情况下,我们需要添加一个检查:如果处于锁定模式,则不能调用任何 setter 方法。

所需的介绍顾问很简单。它需要做的就是保存一个不同的LockMixin实例,并指定引入的接口-在这种情况下,只需Lockable。一个更复杂的示例可能引用了引入拦截器(将被定义为原型):在这种情况下,没有与LockMixin相关的配置,因此我们仅使用new创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

}

我们可以非常简单地应用此顾问程序:它不需要配置。 (但是,必需的:如果没有* IntroductionAdvisor *,则不可能使用IntroductionInterceptor.)像介绍中一样,该 Advisor 必须是按实例的,因为它是有状态的。对于每个建议对象,我们需要LockMixinAdvisor的不同实例,因此需要LockMixin。顾问程序包含建议对象状态的一部分。

我们可以像其他任何顾问一样,使用Advised.addAdvisor()方法或以 XML 配置(建议的方式)以编程方式应用此顾问。下面讨论的所有代理创建选择,包括“自动代理创建器”,都可以正确处理介绍和有状态的混合。

Spring 40.3 Advisor API

在 Spring 中,顾问程序是一个方面,仅包含与切入点表达式关联的单个建议对象。

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

可以在同一 AOP 代理中的 Spring 中混合使用顾问和建议类型。例如,您可以在一个代理配置中使用围绕建议的拦截,抛出建议和在建议之前:Spring 将自动创建必要的拦截器链。

40.4 使用 ProxyFactoryBean 创建 AOP 代理

如果您将 Spring IoC 容器(ApplicationContext 或 BeanFactory)用于业务对象,那么您应该做到了! -您将要使用 Spring 的 AOP FactoryBeans 之一。 (请记住,工厂 bean 引入了一个间接层,使它能够创建其他类型的对象.)

Note

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

在 Spring 中创建 AOP 代理的基本方法是使用* org.springframework.aop.framework.ProxyFactoryBean *。这样可以完全控制要应用的切入点和建议及其 Sequences。但是,如果不需要这样的控制,则有一些更简单的选项比较可取。

40.4.1 Basics

像其他 Spring FactoryBean实现一样,ProxyFactoryBean引入了一个间接级别。如果您使用名称foo定义ProxyFactoryBean,则引用foo的对象不是ProxyFactoryBean实例本身,而是由_ ProxyFactoryBean's implementation of thegetObject()`方法创建的对象。此方法将创建一个包装目标对象的 AOP 代理。

使用ProxyFactoryBean或另一个 IoC 感知类创建 AOP 代理的最重要好处之一是,这意味着建议和切入点也可以由 IoCManagement。这是一项强大的功能,可实现某些其他 AOP 框架难以实现的方法。例如,受益于依赖注入提供的所有可插入性,建议本身可以引用应用程序对象(目标之外,目标应该在任何 AOP 框架中可用)。

40.4.2 JavaBean 属性

与 Spring 提供的大多数FactoryBean实现一样,ProxyFactoryBean类本身就是 JavaBean。其属性用于:

一些关键属性是从org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)继承的。这些关键属性包括:

  • proxyTargetClasstrue(如果要代理目标类,而不是目标类的接口)。如果此属性值设置为true,则将创建 CGLIB 代理(但也请参见第 12.5.3 节“基于 JDK 和 CGLIB 的代理”以下)。

  • optimize:控制是否对通过 CGLIB *创建的代理应用积极的优化。除非人们完全了解相关 AOP 代理如何处理优化,否则不要盲目使用此设置。当前仅用于 CGLIB 代理。它对 JDK 动态代理无效。

  • frozen:如果代理配置为frozen,则不再允许更改配置。这是一个轻微的优化,对于在您不希望调用者在创建代理后能够通过(Advised接口)操纵代理的情况下非常有用。此属性的默认值为false,因此允许进行更改(例如添加其他建议)。

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

  • aopProxyFactory:执行AopProxyFactory使用。提供一种自定义是使用动态代理,CGLIB 还是任何其他代理策略的方式。默认实现将适当选择动态代理或 CGLIB。无需使用此属性;它旨在允许在 Spring 1.1 中添加新的代理类型。

ProxyFactoryBean特有的其他属性包括:

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

  • interceptorNames:要应用的Advisor的字符串数组,拦截器或其他建议名称。Sequences 很重要,先到先得。也就是说,列表中的第一个拦截器将是第一个能够拦截调用的拦截器。

名称是当前工厂中的 bean 名称,包括祖先工厂中的 bean 名称。您不能在这里提及 bean 引用,因为这样做会导致ProxyFactoryBean忽略建议的单例设置。

您可以在拦截器名称后加上星号(*)。这将导致应用所有顾问 Bean,其名称以要应用星号的部分开头。 第 12.5.6 节“使用'全局'顾问”中提供了使用此功能的示例。

  • 单例:无论调用getObject()方法的频率如何,工厂是否应返回单个对象。几种FactoryBean实现提供了这种方法。默认值为true。如果要使用状态通知(例如,对于状态混合),请使用原型建议以及false的单例值。

40.4.3 基于 JDK 和 CGLIB 的代理

本部分是有关ProxyFactoryBean如何选择为特定目标对象(将被代理)创建基于 JDK 和 CGLIB 的代理之一的 Authority 性文档。

Note

在 Spring 的 1.2.x 版和 2.0 版之间,ProxyFactoryBean创建基于 JDK 或 CGLIB 的代理的行为发生了变化。 ProxyFactoryBean现在在自动检测接口方面表现出与TransactionProxyFactoryBean类类似的语义。

如果要代理的目标对象的类(以下简称为目标类)没有实现任何接口,则将创建基于 CGLIB 的代理。这是最简单的情况,因为 JDK 代理基于接口,并且没有接口意味着 JDK 代理甚至不可能。只需插入目标 bean,然后通过interceptorNames属性指定拦截器列表。请注意,即使ProxyFactoryBeanproxyTargetClass属性设置为false,也会创建基于 CGLIB 的代理。 (显然,这没有任何意义,最好将它从 bean 定义中删除,因为它充其量是多余的,而且最糟的是会造成混淆.)

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

如果ProxyFactoryBeanproxyTargetClass属性已设置为true,则将创建基于 CGLIB 的代理。这是有道理的,并且符合最小惊讶原则。即使已将ProxyFactoryBeanproxyInterfaces属性设置为一个或多个完全限定的接口名称,但proxyTargetClass属性设置为true的事实使基于 CGLIB 的代理生效。

如果ProxyFactoryBeanproxyInterfaces属性已设置为一个或多个完全限定的接口名称,则将创建基于 JDK 的代理。创建的代理将实现proxyInterfaces属性中指定的所有接口。如果目标类恰好实现比proxyInterfaces属性中指定的接口更多的接口,那很好,但是返回的代理将不会实现这些其他接口。

如果未设置ProxyFactoryBeanproxyInterfaces属性,但是目标类实现了一个(或多个)接口,那么ProxyFactoryBean将自动检测到目标类确实实现了至少一个接口,然后将创建一个基于 JDK 的代理。实际代理的接口将是目标类实现的所有接口。实际上,这与简单地向proxyInterfaces属性提供目标类实现的每个接口的列表相同。但是,这明显减少了工作量,并且不太容易出现错别字。

40.4.4 代理接口

让我们看一个实际的ProxyFactoryBean的简单示例。此示例涉及:

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

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

  • 一个 AOP 代理 bean 定义,指定目标对象(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属性采用字符串列表:当前工厂中的拦截器或顾问程序的 bean 名称。可以使用顾问程序,拦截器,返回对象之前,之后和引发建议对象。顾问的 Sequences 很重要。

Note

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

可以使用上面的“ person” bean 定义来代替 Person 实现,如下所示:

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

与普通 Java 对象一样,在同一 IoC 上下文中的其他 bean 可以表达对此的强类型依赖性:

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

在此示例中,PersonUser类将公开 Person 类型的属性。就其而言,可以透明地使用 AOP 代理代替“真实”的人实现。但是,其类将是动态代理类。可以将其强制转换为Advised界面(如下所述)。

可以使用匿名* inner 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的对象:如果我们要阻止应用程序上下文的用户获取对未建议对象的引用,或者需要避免与 Spring IoC * autowiring *产生歧义,则很有用。可以说,还有一个优势,就是 ProxyFactoryBean 定义是独立的。但是,有时能够从工厂获得未经建议的目标实际上可能是一个“优势”:例如,在某些测试方案中。

40.4.5 代理类

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

想象一下,在上面的示例中,没有Person接口:我们需要建议一个名为Person的类,该类没有实现任何业务接口。在这种情况下,您可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。只需将上面的 ProxyFactoryBean 的proxyTargetClass属性设置为 true。尽管最好对接口而不是对类进行编程,但是在处理遗留代码时,建议未实现接口的类的功能将非常有用。 (通常,Spring 并不是规定性的.虽然可以轻松地应用良好实践,但避免了强制使用特定方法.)

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

CGLIB 代理通过在运行时生成目标类的子类来工作。 Spring 配置此生成的子类,以将方法调用委托给原始目标:该子类用于实现* Decorator *模式,并编织建议。

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

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

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

CGLIB 代理和动态代理之间几乎没有性能差异。从 Spring 1.0 开始,动态代理要快一些。但是,将来可能会改变。在这种情况下,性能不应作为决定性的考虑因素。

40.4.6 使用“全局”顾问

通过在拦截器名称后附加一个星号,所有具有与该星号之前的部分匹配的 Bean 名称的顾问程序都将添加到顾问程序链中。如果您需要添加一组标准的“全局”顾问,这可以派上用场:

<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 简洁的代理定义

特别是在定义事务代理时,您可能会得到许多类似的代理定义。使用父子 bean 定义和子 bean 定义以及内部 bean 定义可以使代理定义更加简洁明了。

首先,为代理创建一个父类* 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>

它永远不会被实例化,因此实际上可能是不完整的。然后,每个需要创建的代理都只是一个子 bean 定义,它将代理的目标包装为内部 bean 定义,因为无论如何该目标将永远不会单独使用。

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

当然,可以从父模板覆盖属性,例如在这种情况下,事务传播设置:

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

请注意,在上面的示例中,我们已通过使用* abstract 属性将父 bean 定义显式标记为 abstract ,如previously所述,因此它实际上可能没有实例化。默认情况下,应用程序上下文(但不是简单的 bean 工厂)将预先实例化所有单例。因此,重要的是(至少对于单例 bean),如果您有一个(父)bean 定义仅打算用作模板,并且此定义指定了一个类,则必须确保将 abstract 属性设置为 true *,否则应用程序上下文实际上将尝试对其进行实例化。

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

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

以下 Lists 显示了使用一个拦截器和一个顾问程序为目标对象创建代理的过程。目标对象实现的接口将自动被代理:

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

第一步是构造类型为org.springframework.aop.framework.ProxyFactory的对象。您可以使用目标对象创建此对象,如上例所示,或指定要在备用构造函数中代理的接口。

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

ProxyFactory(继承自AdvisedSupport)上还有便捷的方法,使您可以添加其他建议类型,例如 before 并引发建议。 AdvisedSupport 是 ProxyFactory 和 ProxyFactoryBean 的超类。

Tip

在大多数应用程序中,将 AOP 代理创建与 IoC 框架集成在一起是最佳实践。通常,建议您使用 AOP 从 Java 代码外部化配置。

40.7 处理建议的对象

无论创建 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()方法将为已添加到工厂的每种顾问程序,拦截器或其他建议类型返回一个顾问程序。如果添加了顾问,则在此索引处返回的顾问将是您添加的对象。如果您添加了拦截器或其他建议类型,Spring 会将其包装在具有始终返回 true 的切入点的顾问程序中。因此,如果添加了MethodInterceptor,则为此索引返回的顾问程序将是DefaultPointcutAdvisor返回您的MethodInterceptor以及与所有类和方法匹配的切入点。

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

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

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

Note

尽管毫无疑问是合法的使用案例,但是否建议(无双关语)修改 Producing 的业务对象的建议值得怀疑。但是,它在开发中可能非常有用:例如在测试中。我有时发现以拦截器或其他建议的形式添加测试代码,并进入要测试的方法调用中非常有用。 (例如,建议可以进入为该方法创建的事务内:例如,在将事务标记为回滚之前,运行 SQL 以检查数据库是否已正确更新.)

根据创建代理的方式,通常可以设置frozen标志,在这种情况下Advised isFrozen()方法将返回 true,而通过添加或删除来修改建议的任何尝试都将导致AopConfigException。冻结建议对象状态的功能在某些情况下很有用,例如,防止调用代码删除安全拦截器。如果已知不需要修改运行时建议,则在 Spring 1.1 中也可以使用它来进行积极的优化。

40.8 使用“自动代理”功能

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

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

在此模型中,您在 XML bean 定义文件中设置了一些特殊的 bean 定义,以配置自动代理基础结构。这使您只需要声明有资格进行自动代理的目标即可:无需使用ProxyFactoryBean

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

  • 使用在当前上下文中引用特定 bean 的自动代理创建器。

  • 自动代理创建的特殊情况,应该单独考虑;由源级别元数据属性驱动的自动代理创建。

40.8.1 自动代理 bean 定义

org.springframework.aop.framework.autoproxy软件包提供以下标准的自动代理创建者。

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator类是BeanPostProcessor,它会自动为名称与 Literals 值或通配符匹配的 bean 创建 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属性而不是拦截器列表,以允许原型顾问程序具有正确的行为。命名为“拦截器”的可以是顾问或任何建议类型。

一般而言,与自动代理一样,使用BeanNameAutoProxyCreator的要点是将相同的配置一致地应用于多个对象,并且配置量最少。将声明式事务应用于多个对象是一种流行的选择。

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

DefaultAdvisorAutoProxyCreator

一个更通用,功能更强大的自动代理创建者是DefaultAdvisorAutoProxyCreator。这将在当前上下文中自动应用合格的顾问程序,而无需在 autoproxy 顾问程序的 Bean 定义中包括特定的 Bean 名称。它具有与BeanNameAutoProxyCreator相同的一致配置和避免重复的优点。

使用此机制涉及:

  • 指定DefaultAdvisorAutoProxyCreator bean 定义。

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

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

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

通常,自动代理的优点是使调用者或依赖者无法获得不建议的对象。在此 ApplicationContext 上调用 getBean(“ businessObject1”)将返回一个 AOP 代理,而不是目标业务对象。 (前面显示的“ inner bean”惯用语也提供了这一好处.)

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

如果要将相同的建议一致地应用于许多业务对象,则DefaultAdvisorAutoProxyCreator非常有用。基础结构定义到位后,您可以简单地添加新的业务对象,而无需包括特定的代理配置。您还可以非常轻松地加入其他方面,例如跟踪或性能监视方面,而对配置的更改最少。

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

AbstractAdvisorAutoProxyCreator

这是 DefaultAdvisorAutoProxyCreator 的超类。如果顾问程序定义无法为框架DefaultAdvisorAutoProxyCreator的行为提供足够的自定义,则可以通过子类化此类来创建自己的自动代理创建者。

40.8.2 使用元数据驱动的自动代理

自动代理的一种特别重要的类型是由元数据驱动的。这将产生与.NET ServicedComponents类似的编程模型。代替在 EJB 中使用 XML 部署 Descriptors,将事务 Management 和其他企业服务的配置保存在源级别的属性中。

在这种情况下,您可以将DefaultAdvisorAutoProxyCreator与了解元数据属性的 Advisor 结合使用。元数据详细信息保存在候选顾问程序的切入部分中,而不是保存在自动代理创建类本身中。

这确实是DefaultAdvisorAutoProxyCreator的特例,但值得单独考虑。 (支持元数据的代码位于顾问程序中包含的切入点中,而不是 AOP 框架本身.)

JPetStore 示例应用程序的/attributes目录显示了属性驱动的自动代理的使用。在这种情况下,无需使用TransactionProxyFactoryBean。由于使用了支持元数据的切入点,因此只需在业务对象上定义事务属性就足够了。 Bean 定义在/WEB-INF/declarativeServices.xml中包含以下代码。请注意,这是通用的,可以在 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 定义(名称不重要,因此甚至可以省略)将在当前应用程序上下文中获取所有符合条件的切入点。在这种情况下,类型为TransactionAttributeSourceAdvisor的“ transactionAdvisor” bean 定义将应用于带有事务属性的类或方法。 TransactionAttributeSourceAdvisor 通过构造函数依赖项依赖于 TransactionInterceptor。该示例通过自动装配解决了这一问题。 AttributesTransactionAttributeSource取决于org.springframework.metadata.Attributes接口的实现。在此片段中,“属性” Bean 使用 Jakarta Commons Attributes API 来满足此要求,以获取属性信息。 (应用程序代码必须已使用“公共属性”编译任务进行了编译.)

JPetStore 示例应用程序的/annotation目录包含一个由 JDK 1.5Comments 驱动的自动代理的类似示例。以下配置可自动检测 Spring 的TransactionalComments,从而为包含该 Comments 的 bean 生成隐式代理:

<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的定义,尽管该定义不包含在该通用文件中(尽管可能会包含),因为它是特定于应用程序的事务需求的(通常是 JTA,如本例所示,或者是 Hibernate,JDO 或 JDBC) :

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

Tip

如果只需要声明式事务 Management,则使用这些通用 XML 定义将导致 Spring 自动使用事务属性代理所有类或方法。您无需直接使用 AOP,并且编程模型与.NET ServicedComponents 相似。

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

  • 定义您的自定义属性。

  • 指定带有必要建议的顾问,包括由类或方法上的自定义属性的存在触发的切入点。您可能可以使用现有建议,仅实现一个实现自定义属性的静态切入点即可。

对于每个建议的类(例如,mixin),此类顾问可能是唯一的:只需将它们定义为原型,而不是单例 bean 定义。例如,如上所示,可以将上面所示的 Spring 测试套件中的LockMixin简介拦截器与属性驱动的切入点一起使用,以将 mixin 作为目标。我们使用通过 JavaBean 属性配置的通用DefaultPointcutAdvisor

<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切入点可以是单例定义,因为它不保留单个建议对象的状态。

40.9 使用 TargetSources

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

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

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

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

Tip

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

40.9.1 热插拔目标源

org.springframework.aop.target.HotSwappableTargetSource的存在是为了允许 AOP 代理服务器的目标切换,同时允许调用者保留对其的引用。

更改目标源的目标会立即生效。 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 的引用的 Client 将不知道更改,但将立即开始达到新目标。

尽管此示例未添加任何建议-也不必添加建议以使用TargetSource-当然,任何TargetSource都可以与任意建议结合使用。

40.9.2 合并目标源

使用池目标源提供了与 Stateless 会话 EJB 相似的编程模型,在 Stateless 会话 EJB 中,维护了相同实例的池,方法调用将释放池中的对象。

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

Spring 为 Commons Pool 2.2 提供了开箱即用的支持,它提供了相当有效的池实现。您需要在应用程序的 Classpath 上的公共池 Jar 才能使用此功能。也可以子类org.springframework.aop.target.AbstractPoolingTargetSource来支持任何其他池化 API。

Note

从 Spring Framework 4.2 开始,Commons Pool 1.5 也受支持,但已弃用。

示例配置如下所示:

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

在这种情况下,“ myInterceptor”是需要在同一 IoC 上下文中定义的拦截器的名称。但是,没有必要指定拦截器来使用池。如果您只希望池化,而没有其他建议,则完全不要设置 interceptorNames 属性。

可以配置 Spring,以便能够将任何池对象强制转换为org.springframework.aop.target.PoolingConfig接口,该接口通过介绍来公开有关池的配置和当前大小的信息。您需要定义一个顾问,如下所示:

<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 中的侦听器名称列表中。

演员表如下所示:

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

Note

通常不需要合并 Stateless 服务对象。我们不认为它应该是默认选择,因为大多数 Stateless 对象自然是线程安全的,并且如果缓存了资源,实例池会成问题。

使用自动代理可以简化池化。可以设置任何自动代理创建者使用的 TargetSources。

40.9.3 原型目标源

设置“原型”目标源类似于池化 TargetSource。在这种情况下,将在每次方法调用时创建目标的新实例。尽管在现代 JVM 中创建新对象的成本并不高,但是连接新对象(满足其 IoC 依赖关系)的成本可能会更高。因此,没有充分的理由就不应使用此方法。

为此,您可以按如下所示修改上面显示的poolTargetSource定义。 (为清楚起见,我也更改了名称.)

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

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

40.9.4 ThreadLocal 目标源

如果您需要为每个传入请求(每个线程)创建一个对象,则ThreadLocal目标源很有用。 ThreadLocal的概念提供了 JDK 范围的功能,可以透明地将资源与线程一起存储。设置ThreadLocalTargetSource几乎与其他类型的目标源所说明的相同:

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

Note

在多线程和多类加载器环境中错误地使用 ThreadLocals 时,会遇到严重的问题(可能导致内存泄漏)。人们应该始终考虑在其他一些类中包装 threadlocal,并且永远不要直接使用ThreadLocal本身(当然,在包装类中除外)。同样,应该始终记住正确设置和取消设置线程本地资源(在后者仅涉及到对ThreadLocal.set(null)的调用)。在任何情况下都应进行取消设置,因为不取消设置可能会导致出现问题。 Spring 的 ThreadLocal 支持为您做到这一点,应该始终考虑使用 ThreadLocals 而不使用其他适当的处理代码。

40.10 定义新的建议类型

Spring AOP 被设计为可扩展的。尽管目前在内部使用拦截实现策略,但除了在建议之前,抛出建议和返回建议之后围绕建议的现成拦截之外,还可以支持任意建议类型。

org.springframework.aop.framework.adapter软件包是 SPI 软件包,允许在不更改核心框架的情况下添加对新的自定义建议类型的支持。自定义Advice类型的唯一限制是它必须实现org.aopalliance.aop.Advice标签接口。

请参考org.springframework.aop.framework.adapter软件包的 Javadocs 以获得更多信息。

40.11 更多资源

请参考 Spring 示例应用程序以获取 Spring AOP 的更多示例:

  • JPetStore 的默认配置说明了使用TransactionProxyFactoryBean进行声明式事务 Management。

  • JPetStore 的/attributes目录说明了属性驱动的声明式事务 Management 的用法。