12. Spring AOP API

12.1 简介

前一章描述了Spring使用@AspectJ和基于模式的方面定义对AOP的支持。在本章中,我们将讨论较低级别的Spring AOP API以及Spring 1.2应用程序中使用的AOP支持。对于新应用程序,我们建议使用前一章中描述的Spring 2.0及更高版本的AOP支持,但在使用现有应用程序时,或者在阅读书籍和文章时,您可能会遇到Spring 1.2样式示例。 Spring 4.0向后兼容Spring 1.2,本章中描述的所有内容在Spring 4.0中都得到了完全支持。

12.2 Spring中的Pointcut API

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

12.2.1 概念

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

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

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut 接口拆分为两部分允许重用类和方法匹配部分,以及细粒度合成操作(例如使用另一个方法匹配器执行 "union" )。

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参数matches方法对给定方法返回true,并且MethodMatcher的 isRuntime() 方法返回true,则将在每次方法调用时调用3参数匹配方法。这使切入点能够在执行目标通知之前立即查看传递给方法调用的参数。

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

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

12.2.2 关于切入点的操作

Spring支持对切入点的操作:特别是联合和交集。

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

  • 交叉表示两个切入点匹配的方法。

  • 联盟通常更有用。

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

12.2.3 AspectJ表达式切入点

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

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

12.2.4 便利切入点实现

Spring提供了几种方便的切入点实现。有些可以开箱即用;其他的意图是在特定于应用程序的切入点中进行子类化。

静态切入点

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

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

正则表达式切入点

指定静态切入点的一种显而易见的方法是正则表达式。除Spring之外的几个AOP框架使这成为可能。 org.springframework.aop.support.JdkRegexpMethodPointcut 是一个通用正则表达式切入点,使用JDK 1.4中的正则表达式支持。

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

用法如下所示:

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

Spring提供了一个便利类 RegexpMethodPointcutAdvisor ,它允许我们引用一个建议(记住一个建议可以是一个拦截器,在建议之前,抛出建议等)。在幕后,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可以与任何建议类型一起使用。

属性驱动的切入点

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

动态切入点

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

主要的例子是 control flow 切入点。

控制流量切入点

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

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

12.2.5 切入点超类

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

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

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

还有动态切入点的超类。

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

12.2.6 自定义切入点

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

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

12.3 Spring中的建议API

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

12.3.1 忠告生命周期

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

每类建议最常使用。它适用于 Transaction 顾问等通用建议。这些不依赖于代理对象的状态或添加新状态;他们只是按照方法和论点行事。

每个实例的建议适用于介绍,以支持mixin。在这种情况下,建议将状态添加到代理对象。

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

12.3.2 Spring中的建议类型

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

围绕建议拦截

Spring中最基本的建议类型是拦截建议。

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

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() 方法的调用。这沿拦截器链向下进入连接点。大多数拦截器都会调用此方法,并返回其返回值。但是,与任何around建议一样,MethodInterceptor可以返回不同的值或抛出异常,而不是调用proceed方法。但是,你没有充分的理由不想这样做!

MethodInterceptors提供与其他符合AOP Alliance标准的AOP实现的互操作性。本节其余部分讨论的其他建议类型实现了常见的AOP概念,但是采用Spring特定的方式。虽然使用最具体的建议类型有一个优势,但如果您可能希望在另一个AOP框架中运行该方面,请坚持使用MethodInterceptor建议。请注意,切入点目前在框架之间不可互操作,AOP联盟目前不定义切入点接口。

在建议之前

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

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

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

public interface MethodBeforeAdvice extends BeforeAdvice {

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

请注意,返回类型是 void 。在建议之前可以在连接点执行之前插入自定义行为,但不能更改返回值。如果before advice抛出异常,这将中止拦截器链的进一步执行。异常将传播回拦截链。如果未选中,或者在被调用方法的签名上,它将直接传递给客户端;否则它将被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;
    }
}

之前的建议可用于任何切入点。

抛出建议

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

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

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

如果抛出 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 。可以在单个类中组合任意数量的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方法本身抛出异常,它将覆盖原始异常(即更改抛出给用户的异常)。覆盖异常通常是RuntimeException;这与任何方法签名兼容。但是,如果throws-advice方法抛出一个已检查的异常,则它必须匹配目标方法的声明异常,因此在某种程度上耦合到特定的目标方法签名。不要抛出与目标方法签名不兼容的未声明的已检查异常!

抛出建议可用于任何切入点。

返回建议后

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

public interface AfterReturningAdvice extends Advice {

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

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

返回通知后的以下内容计算所有未抛出异常的成功方法调用:

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

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

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

介绍建议

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

这说明了一个混合。我们希望能够将建议对象转换为Lockable,无论其类型如何,并调用锁定和解锁方法。如果我们调用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 。)与介绍一样,顾问必须是每个实例,因为它是有状态的。对于每个建议对象,我们需要 LockMixinAdvisor 的不同实例,因此需要 LockMixin 。顾问包括建议对象的状态的一部分。

我们可以使用 Advised.addAdvisor() 方法或(建议的方式)以编程方式应用此顾问程序,与任何其他顾问程序一样。下面讨论的所有代理创建选项,包括 "auto proxy creators," 正确处理引入和有状态混合。

12.4 Spring中的Advisor API

在Spring中,Advisor是一个只包含与切入点表达式关联的建议对象的方面。

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

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

12.5 使用ProxyFactoryBean创建AOP代理

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

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

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

12.5.1 基础知识

与其他Spring FactoryBean 实现一样, ProxyFactoryBean 引入了一个间接层。如果定义名称为 fooProxyFactoryBean ,则引用 foo 的对象不是 ProxyFactoryBean 实例本身,而是 ProxyFactoryBean 实现 getObject() 方法创建的对象。此方法将创建包装目标对象的AOP代理。

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

12.5.2 JavaBean属性

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

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

  • proxyTargetClasstrue 如果要代理目标类,而不是目标类的接口。如果此属性值设置为 true ,则将创建CGLIB代理(但另请参见 Section 12.5.3, “JDK- and CGLIB-based proxies” )。

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

  • frozen :如果代理配置为 frozen ,则不再允许更改配置。这对于稍微优化和在您不希望调用者在创建代理之后能够操作代理(通过 Advised 接口)的情况有用。此属性的默认值为 false ,因此允许添加其他建议等更改。

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

ProxyFactoryBean 特有的其他属性包括:

  • proxyInterfaces :String接口名称数组。如果未提供,则将使用目标类的CGLIB代理(但另请参见 Section 12.5.3, “JDK- and CGLIB-based proxies” )。

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

名称是当前工厂中的bean名称,包括来自祖先工厂的bean名称。你不能在这里提到bean引用,因为这样做会导致 ProxyFactoryBean 忽略通知的单例设置。

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

  • singleton:无论调用 getObject() 方法的频率如何,工厂是否应该返回单个对象。几个 FactoryBean 实现提供了这样的方法。默认值为 true 。如果您想使用有状态建议 - 例如,对于有状态的mixins - 使用原型建议以及单个值 false

12.5.3 基于JDK和CGLIB的代理

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

关于创建基于JDK或CGLIB的代理的ProxyFactoryBean的行为在Spring的1.2.x和2.0版本之间发生了变化。现在,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 属性实现的每个接口的列表相同。但是,它的工作量明显减少,并且不太容易出现错别字。

12.5.4 代理接口

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

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

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

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

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

请注意 interceptorNames 属性采用String列表:当前工厂中拦截器或顾问程序的bean名称。顾问,拦截器,之前,返回并抛出建议对象后可以使用。顾问的排序很重要。

您可能想知道为什么列表不包含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代理来代替 "real" 人实现。但是,它的类将是一个动态代理类。可以将其转换为 Advised 界面(下面讨论)。

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

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

这样做的好处是,只有一个 Person 类型的对象:如果我们想要阻止应用程序上下文的用户获取对未建议对象的引用,或者需要避免使用Spring IoC自动装配的任何歧义,这将非常有用。还有一个优点是ProxyFactoryBean定义是自包含的。但是,有时能够从工厂获得未建议的目标实际上可能是一个优势:例如,在某些测试场景中。

12.5.5 代理类

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

想象一下,在上面的示例中,没有 Person 接口:我们需要建议一个名为 Person 的类,它没有实现任何业务接口。在这种情况下,您可以将Spring配置为使用CGLIB代理,而不是动态代理。只需将上面的ProxyFactoryBean上的 proxyTargetClass 属性设置为true即可。虽然最好是编程接口而不是类,但在使用遗留代码时,建议不实现接口的类的能力会很有用。 (一般来说,Spring不是规定性的。虽然它可以很容易地应用好的实践,但它避免强制使用特定的方法。)

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

CGLIB代理通过在运行时生成目标类的子类来工作。 Spring将这个生成的子类配置为委托对原始目标的方法调用:子类用于实现Decorator模式,在通知中编织。

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

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

  • 无需将CGLIB添加到类路径中。从Spring 3.2开始,CGLIB被重新打包并包含在spring-core JAR中。换句话说,基于CGLIB的AOP将像JDK动态代理一样工作 "out of the box" 。

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

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

12.6 简明的代理定义

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

首先为代理创建父,模板,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,否则应用程序上下文将实际尝试预先实例化它。

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

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

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

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

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

您可以添加建议(使用拦截器作为专门的建议)和/或顾问,并在ProxyFactory的生命周期中对其进行操作。如果添加IntroductionInterceptionAroundAdvisor,则可以使代理实现其他接口。

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

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

12.8 操纵建议的对象

但是,您创建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。如果添加了Advisor,则此索引处返回的顾问程序将是您添加的对象。如果您添加了一个拦截器或其他建议类型,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);

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

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

12.9 使用“自动代理”功能

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

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

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

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

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

  • 自动代理创建的一个特例,值得单独考虑;由...驱动的自动代理创建源级元数据属性。

12.9.1 Autoproxy bean定义

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

BeanNameAutoProxyCreator

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

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

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

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

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

DefaultAdvisorAutoProxyCreator

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

使用此机制涉及:

  • 指定 DefaultAdvisorAutoProxyCreator bean定义。

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

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

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

一般的自动代理具有使调用者或依赖者无法获得未建议的对象的优点。在此ApplicationContext上调用getBean(“businessObject1”)将返回AOP代理,而不是目标业务对象。 (前面所示的“内 beans ”成语也提供了这种好处。)

<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

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

一种特别重要的自动代理类型由元数据驱动。这产生了与.NET ServicedComponents 类似的编程模型。不是在XML描述符中定义元数据,而是在源级属性中保存事务管理和其他企业服务的配置。

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

这实际上是 DefaultAdvisorAutoProxyCreator 的一个特例,但值得自己考虑。 (元数据感知代码位于顾问程序中包含的切入点中,而不是AOP框架本身。)

JPetStore示例应用程序的 /attributes 目录显示了属性驱动的自动代理的使用。在这种情况下,不需要使用 TransactionProxyFactoryBean 。由于使用了元数据感知切入点,仅仅在业务对象上定义事务属性就足够了。 beans 子定义包括以下代码,在 /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 接口的实现。在这个片段中, "attributes" bean使用Jakarta Commons Attributes API来获取属性信息。 (必须使用Commons Attributes编译任务编译应用程序代码。)

JPetStore示例应用程序的 /annotation 目录包含一个由JDK 1.5注释驱动的自动代理的类似示例。以下配置可以自动检测Spring的 Transactional 注释,从而导致包含该注释的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"/>

如果只需要声明式事务管理,那么使用这些通用XML定义将导致Spring自动代理所有具有事务属性的类或方法。您不需要直接使用AOP,编程模型类似于.NET ServicedComponents。

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

  • 定义自定义属性。

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

这样的顾问可能对每个建议的类都是唯一的(例如,mixins):它们只需要被定义为原型,而不是单独的bean定义。例如,如上所示,Spring测试套件中的 LockMixin 介绍拦截器可以与通用_684740一起使用:

<bean id="lockMixin" class="test.mixin.LockMixin" scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"
        scope="prototype">
    <constructor-arg ref="lockMixin"/>
</bean>

请注意, lockMixinlockableAdvisor 都被定义为原型。

12.10 使用TargetSources

Spring提供了TargetSource的概念,在 org.springframework.aop.TargetSource 界面中表示。该接口负责返回实现连接点的 "target object" 。每次AOP代理处理方法调用时,都会要求 TargetSource 实现一个目标实例。

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

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

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

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

12.10.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的引用的客户端将不知道该更改,但会立即开始命中新目标。

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

12.10.2 汇集目标来源

使用池化目标源为无状态会话EJB提供了类似的编程模型,其中维护了相同实例的池,方法调用将释放池中的空闲对象。

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

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

Commons Pool 1.5也受支持,但在Spring Framework 4.2中已弃用。

示例配置如下所示:

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

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

使用自动代理可以实现更简单的池化。可以设置任何自动代理创建者使用的TargetSource。

12.10.3 原型目标来源

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

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

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

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

12.10.4 ThreadLocal目标来源

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

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

ThreadLocals在多线程和多类加载器环境中错误地使用它们时会出现严重问题(可能导致内存泄漏)。应该总是考虑将threadlocal包装在其他类中,而不是直接使用ThreadLocal本身(当然除了包装类)。此外,应始终记住正确设置和取消设置(后者只涉及对ThreadLocal.set(null)的调用)线程本地的资源。在任何情况下都应该进行取消设置,因为不取消设置可能会导致有问题的行为。 Spring的ThreadLocal支持为您执行此操作,应始终考虑使用ThreadLocals而不使用其他正确的处理代码。

12.11 定义新的建议类型

Spring AOP旨在可扩展。虽然拦截实现策略目前在内部使用,但除了围绕建议的开箱即用拦截之外,还可以支持任意建议类型,之前,抛出建议和返回建议之后。

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

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

12.12 更多资源

有关Spring AOP的更多示例,请参阅Spring示例应用程序:

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

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

Updated at: 5 months ago
11.9. 更多资源Table of contentIV. 测试
Comment
You are not logged in.

There are no comments.