Spring AOP: Spring之面向方面编程

5.1. 概念

面向方面编程 (AOP) 提供从另外一个角度来考虑程序结构以完善面向对象编程(OOP)。 面向对象将应用程序分解成 各个层次的对象,而AOP将程序分解成各个方面 或者说 关注点 。 这使得能够模块化诸如事务管理等这些横切多个对象的关注点。(这些关注点术语称做 横切关注点。)java

Spring的一个关键组件就是AOP框架。 Spring IoC容器(BeanFactory 和ApplicationContext)并不依赖于AOP, 这意味着若是你不须要使用,AOP你能够不用,AOP完善了Spring IoC,使之成为一个有效的中间件解决方案,。web

AOP在Spring中的使用:正则表达式

  • 提供声明式企业服务,特别是做为EJB声明式服务的替代品。这些服务中最重要的是 声明式事务管理,这个服务创建在Spring的事务管理抽象之上。spring

  • 容许用户实现自定义的方面,用AOP完善他们的OOP的使用。数据库

这样你能够把Spring AOP看做是对Spring的补充,它使得Spring不须要EJB就能提供声明式事务管理;或者 使用Spring AOP框架的所有功能来实现自定义的方面。编程

若是你只使用通用的声明式服务或者预先打包的声明式中间件服务如pooling,你能够不直接使用 Spring AOP,而且跳过本章的大部份内容.

5.1.1. AOP概念

让咱们从定义一些重要的AOP概念开始。这些术语不是Spring特有的。不幸的是,Spring的术语 不是特别地直观。并且,若是Spring使用本身的术语,这将令人更加迷惑。设计模式

  • 方面(Aspect): 一个关注点的模块化,这个关注点实现可能 另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。数组

  • 链接点(Joinpoint): 程序执行过程当中明确的点,如方法的调 用或特定的异常被抛出。缓存

  • 通知(Advice): 在特定的链接点,AOP框架执行的动做。各类类 型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架 包括Spring都是以拦截器作通知模型,维护一个“围绕”链接点的拦截器 链。安全

  • 切入点(Pointcut): 指定一个通知将被引起的一系列链接点 的集合。AOP框架必须容许开发者指定切入点:例如,使用正则表达式。

  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring容许引入新的接口到任何被通知的对象。例如,你可使用一个引入使任何对象实现 IsModified接口,来简化缓存。

  • 目标对象(Target Object): 包含链接点的对象。也被称做 被通知被代理对象。

  • AOP代理(AOP Proxy): AOP框架建立的对象,包含通知。 在Spring中,AOP代理能够是JDK动态代理或者CGLIB代理。

  • 织入(Weaving): 组装方面来建立一个被通知对象。这能够在编译时 完成(例如使用AspectJ编译器),也能够在运行时完成。Spring和其余纯Java AOP框架同样, 在运行时完成织入。

各类通知类型包括:

  • Around通知: 包围一个链接点的通知,如方法调用。这是最 强大的通知。Aroud通知在方法调用先后完成自定义的行为。它们负责选择继续执行链接点或经过 返回它们本身的返回值或抛出异常来短路执行。

  • Before通知: 在一个链接点以前执行的通知,但这个通知 不能阻止链接点前的执行(除非它抛出一个异常)。

  • Throws通知: 在方法抛出异常时执行的通知。Spring提供 强类型的Throws通知,所以你能够书写代码捕获感兴趣的异常(和它的子类),不须要从Throwable 或Exception强制类型转换。

  • After returning通知: 在链接点正常完成后执行的通知, 例如,一个方法正常返回,没有抛出异常。

Around通知是最通用的通知类型。大部分基于拦截的AOP框架,如Nanning和JBoss4,只提供 Around通知。

如同AspectJ,Spring提供全部类型的通知,咱们推荐你使用最为合适的通知类型来实现需 要的行为。例如,若是只是须要用一个方法的返回值来更新缓存,你最好实现一个after returning 通知而不是around通知,虽然around通知也能完成一样的事情。使用最合适的通知类型使编程模型变 得简单,并能减小潜在错误。例如你不须要调用在around通知中所需使用的的MethodInvocation的 proceed()方法,所以就调用失败。

切入点的概念是AOP的关键,使AOP区别于其它使用拦截的技术。切入点使通知独立于OO的 层次选定目标。例如,提供声明式事务管理的around通知能够被应用到跨越多个对象的一组方法上。 所以切入点构成了AOP的结构要素。

5.1.2. Spring AOP的功能

Spring AOP用纯Java实现。它不须要特别的编译过程。Spring AOP不须要控制类装载器层次, 所以适用于J2EE web容器或应用服务器。

Spring目前支持拦截方法调用。成员变量拦截器没有实现,虽然加入成员变量拦截器支持并不破坏 Spring AOP核心API。

成员变量拦截器在违反OO封装原则方面存在争论。咱们不认为这在应用程序开发中是明智的。如 果你须要使用成员变量拦截器,考虑使用AspectJ。

Spring提供表明切入点或各类通知类型的类。Spring使用术语advisor来 表示表明方面的对象,它包含一个通知和一个指定特定链接点的切入点。

各类通知类型有MethodInterceptor (来自AOP联盟的拦截器API)和定义在org.springframework.aop包中的 通知接口。全部通知必须实现org.aopalliance.aop.Advice标签接口。 取出就可以使用的通知有 MethodInterceptor、 ThrowsAdvice、 BeforeAdvice和 AfterReturningAdvice。咱们将在下面详细讨论这些通知类型。

Spring实现了AOP联盟的拦截器接口( http://www.sourceforge.net/projects/aopalliance). Around通知必须实现AOP联盟的org.aopalliance.intercept.MethodInterceptor 接口。这个接口的实现能够运行在Spring或其余AOP联盟兼容的实现中。目前JAC实现了AOP联盟的接 口,Nanning和Dynaop可能在2004年早期实现。

Spring实现AOP的途径不一样于其余大部分AOP框架。它的目标不是提供及其完善的AOP实现( 虽然Spring AOP很是强大);而是提供一个和Spring IoC紧密整合的AOP实现,帮助解决企业应用 中的常见问题。 所以,例如Spring AOP的功能一般是和Spring IoC容器联合使用的。AOP通知是用普通 的bean定义语法来定义的(虽然可使用"autoproxying"功能);通知和切入点自己由Spring IoC 管理:这是一个重要的其余AOP实现的区别。有些事使用Spring AOP是没法容易或高效地实现,好比通知 很是细粒度的对象。这种状况AspectJ多是最合适的选择。可是,咱们的经验是Spring针对J2EE应 用中大部分能用AOP解决的问题提供了一个优秀的解决方案。

5.1.3. Spring中AOP代理

Spring默认使用JDK动态代理实现AOP代理。这使得任何接口或 接口的集合可以被代理。

Spring也能够是CGLIB代理。这能够代理类,而不是接口。若是业务对象没有实现一个接口, CGLIB被默认使用。可是做为一针对接口编程而不是类编程良好实践,业务对象 一般实现一个或多个业务接口。

也能够强制使用CGLIB:咱们将在下面讨论,而且会解释为何你会要这么作。

Spring 1.0后,Spring可能提供额外的AOP代理的类型,包括彻底生成的类。这将不会影响 编程模型。

 

5.2. Spring的切入点

让咱们看看Spring如何处理切入点这个重要的概念。

5.2.1. 概念

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个参数的匹配方法将在每次方法调用的时候被调用。这使 切入点可以在目标通知被执行以前当即查看传递给方法调用的参数。

大部分MethodMatcher都是静态的,意味着isRuntime()方法 返回false。这种状况下3个参数的匹配方法永远不会被调用。

若是可能,尽可能使切入点是静态的,使当AOP代理被建立时,AOP框架可以缓存切入点的 测试结果。

5.2.2. 切入点的运算

Spring支持的切入点的运算有: 值得注意的是

并表示只要任何一个切入点匹配的方法。

交表示两个切入点都要匹配的方法。

并一般比较有用。

切入点能够用org.springframework.aop.support.Pointcuts 类的静态方法来组合,或者使用同一个包中的ComposablePointcut类。

5.2.3. 实用切入点实现

Spring提供几个实用的切入点实现。一些能够直接使用。另外一些须要子类化来实现应用相 关的切入点。

5.2.3.1. 静态切入点

静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够知足大多数状况 的使用。Spring能够只在方法第一次被调用的时候计算静态切入点,不须要在每次方法调用 的时候计算。

让咱们看一下Spring提供的一些静态切入点的实现。

5.2.3.1.1. 正则表达式切入点

一个很显然的指定静态切入点的方法是正则表达式。除了Spring之外,其它的AOP框架也实 现了这一点。org.springframework.aop.support.RegexpMethodPointcut 是一个通用的正则表达式切入点,它使用Perl 5的正则表达式的语法。

使用这个类你能够定义一个模式的列表。若是任何一个匹配,那个切入点将被计算成 true。(因此结果至关因而这些切入点的并集)。

用法以下:

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

RegexpMethodPointcut一个实用子类, RegexpMethodPointcutAdvisor, 容许咱们同时引用一个通知。 (记住通知能够是拦截器,before通知,throws通知等等。)这简化了bean的装配,由于一个bean 能够同时看成切入点和通知,以下所示:

<bean id="settersAndAbsquatulateAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="interceptor">
        <ref local="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor能够用于任何通知类型。

RegexpMethodPointcut类须要Jakarta ORO正则表达式包。
5.2.3.1.2. 属性驱动的切入点

一类重要的静态切入点是元数据驱动的 切入点。 它使用元数据属性的值:典型地,使用源代码级元数据。

5.2.3.2. 动态切入点

动态切入点的演算代价比静态切入点高的多。它们不只考虑静态信息,还要考虑方法的 参数。这意味着它们必须在每次方法调用的时候都被计算;而且不能缓存结果 ,由于参数是变化的。

这个主要的例子就是控制流切入点。

5.2.3.2.1. 控制流切入点

Spring的控制流切入点概念上和AspectJ的cflow 切入点一致,虽然没有其那么强大(当前没有办法指定一个切入点在另外一个切入点后执行)。 一个控制流切入点匹配当前的调用栈。例如,链接点被 com.mycompany.web包或者 SomeCaller类中一个方法调用的时候,触发该切入点。控制流切入点的实现类是 org.springframework.aop.support.ControlFlowPointcut。

注意

控制流切入点是动态切入点中计算代价最高的。Java 1.4中, 它的运行开销是其余动态切入点的5倍。在Java 1.3中则超过10倍。

5.2.4. 切入点超类

Spring提供很是实用的切入点的超类帮助你实现你本身的切入点。

由于静态切入点很是实用,你极可能子类化StaticMethodMatcherPointcut,以下所示。 这只须要实现一个抽象方法(虽然能够改写其它的方法来自定义行为)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

固然也有动态切入点的超类。

Spring 1.0 RC2或以上版本,自定义切入点能够用于任何类型的通知。

5.2.5. 自定义切入点

由于Spring中的切入点是Java类,而不是语言特性(如AspectJ),所以能够定义自定义切入点, 不管静态仍是动态。可是,没有直接支持用AspectJ语法书写的复杂的切入点表达式。不过, Spring的自定义切入点也能够任意的复杂。

后续版本的Spring可能象JA同样C提供”语义切入点“的支持:例如,“全部更改目标对象 实例变量的方法”。

 

5.3. Spring的通知类型

如今让咱们看看Spring AOP是如何处理通知的。

5.3.1. 通知的生命周期

Spring的通知能够跨越多个被通知对象共享,或者每一个被通知对象有本身的通知。这分别对应 per-classper-instance 通知。

Per-class通知使用最为普遍。它适合于通用的通知,如事务adisor。它们不依赖被代理 的对象的状态,也不添加新的状态。它们仅仅做用于方法和方法的参数。

Per-instance通知适合于导入,来支持混入(mixin)。在这种状况下,通知添加状态到 被代理的对象。

能够在同一个AOP代理中混合使用共享和per-instance通知。

5.3.2. Spring中通知类型

Spring提供几种现成的通知类型并可扩展提供任意的通知类型。让咱们看看基本概念和 标准的通知类型。

5.3.2.1. Interception around advice

Spring中最基本的通知类型是interception around advice .

Spring使用方法拦截器的around通知是和AOP联盟接口兼容的。实现around通知的 类须要实现接口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,和任何around通知同样,能够返回不一样的值或者抛出一个异常,而 不调用proceed方法。可是,没有好的缘由你要这么作。

MethodInterceptor提供了和其余AOP联盟的兼容实现的交互能力。这一节下面 要讨论的其余的通知类型实现了AOP公共的概念,可是以Spring特定的方式。虽然使用特定 通知类型有不少优势,但若是你可能须要在其余的AOP框架中使用,请坚持使用MethodInterceptor around通知类型。注意目前切入点不能和其它框架交互操做,而且AOP联盟目前也没有定义切入 点接口。

5.3.2.2. Before通知

Before通知是一种简单的通知类型。 这个通知不须要一个MethodInvocation对象,由于它只在进入一个方法 前被调用。

Before通知的主要优势是它不须要调用proceed() 方法, 所以没有无心中忘掉继续执行拦截器链的可能性。

MethodBeforeAdvice接口以下所示。 (Spring的API设计容许成员变量的before通知,虽然通常的对象均可以应用成员变量拦截,但Spring 有可能永远不会实现它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

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

注意返回类型是void。 Before通知能够在链接点执行以前 插入自定义的行为,可是不能改变返回值。若是一个before通知抛出一个异常,这将中断拦截器 链的进一步执行。这个异常将沿着拦截器链后退着向上传播。若是这个异常是unchecked的,或者 出如今被调用的方法的签名中,它将会被直接传递给客户代码;不然,它将被AOP代理包装到一个unchecked 的异常里。

下面是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; 
    }
}
Before通知能够被用于任何类型的切入点。

5.3.2.3. Throws通知

若是链接点抛出异常,Throws通知 在链接点返回后被调用。Spring提供强类型的throws通知。注意这意味着 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异常, 下面的通知会被调用。和上面的通知不同,它声明了四个参数,因此它能够访问被调用的方法,方法的参数 和目标对象:

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最后一个例子演示了如何在一个类中使用两个方法来同时处理 RemoteException和ServletException 异常。任意个数的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 will all arguments
    }
}
Throws通知可被用于任何类型的切入点。

5.3.2.4. After Returning通知

Spring中的after returning通知必须实现 org.springframework.aop.AfterReturningAdvice 接口,以下所示:

public interface AfterReturningAdvice extends Advice {

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

After returning通知能够访问返回值(不能改变)、被调用的方法、方法的参数和 目标对象。

下面的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;
    }
}

这方法不改变执行路径。若是它抛出一个异常,这个异常而不是返回值将被沿着拦截器链 向上抛出。

After returning通知可被用于任何类型的切入点。

5.3.2.5. Introduction通知

Spring将introduction通知看做一种特殊类型的拦截通知。

Introduction须要实现IntroductionAdvisor, 和IntroductionInterceptor接口:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

继承自AOP联盟MethodInterceptor接口的 invoke()方法必须实现导入:也就是说,若是被调用的方法是在 导入的接口中,导入拦截器负责处理这个方法调用,它不能调用proceed() 方法。

Introduction通知不能被用于任何切入点,由于它只能做用于类层次上,而不是方法。 你能够只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法:

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

    ClassFilter getClassFilter();

    IntroductionInterceptor getIntroductionInterceptor();

    Class[] getInterfaces();
}

这里没有MethodMatcher,所以也没有和导入通知关联的 切入点。只有类过滤是合乎逻辑的。

getInterfaces()方法返回advisor导入的接口。

让咱们看看一个来自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的设计是将导入 委托到真正实现导入接口的接口,隐藏完成这些工做的拦截器。委托可使用构造方法参数 设置到任何对象中;默认的委托就是本身(当无参数的构造方法被使用时)。这样在下面的 例子里,委托是DelegatingIntroductionInterceptor的子类 LockMixin。给定一个委托(默认是自身)的 DelegatingIntroductionInterceptor实例寻找被这个委托(而不 是IntroductionInterceptor)实现的全部接口,并支持它们中任何一个导入。子类如 LockMixin也可能调用suppressInterflace(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就足够了,若是是导入的方法, DelegatingIntroductionInterceptor实现会调用委托方法, 不然继续沿着链接点处理。在如今的状况下,咱们须要添加一个检查:在上锁 状态下不能调用setter方法。

所需的导入advisor是很简单的。只有保存一个独立的 LockMixin实例,并指定导入的接口,在这里就是 Lockable。一个稍微复杂一点例子可能须要一个导入拦截器(能够 定义成prototype)的引用:在这种状况下,LockMixin没有相关配置,因此咱们简单地 使用new来建立它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

咱们能够很是简单地使用这个advisor:它不须要任何配置。(可是,有一点 必要的:就是不可能在没有IntroductionAdvisor 的状况下使用IntroductionInterceptor。) 和导入同样,一般 advisor必须是针对每一个实例的,而且是有状态的。咱们会有不一样的的LockMixinAdvisor 每一个被通知对象,会有不一样的LockMixin。 advisor组成了被通知对象的状态的一部分。

和其余advisor同样,咱们可使用 Advised.addAdvisor() 方法以编程地方式使用这种advisor,或者在XML中配置(推荐这种方式)。 下面将讨论全部代理建立,包括“自动代理建立者”,选择代理建立以正确地处理导入和有状态的混入。

 

5.4. Spring中的advisor

在Spring中,一个advisor就是一个aspect的完整的模块化表示。 通常地,一个advisor包括通知和切入点。

撇开导入这种特殊状况,任何advisor可被用于任何通知。 org.springframework.aop.support.DefaultPointcutAdvisor 是最通用的advisor类。例如,它能够和MethodInterceptor、 BeforeAdvice或者ThrowsAdvice一块儿使 用。

Spring中能够将advisor和通知混合在一个AOP代理中。例如,你能够在一个代理配置中 使用一个对around通知、throws通知和before通知的拦截:Spring将自动建立必要的拦截器链。

 

5.5. 用ProxyFactoryBean建立AOP代理

若是你在为你的业务对象使用Spring的IoC容器(例如ApplicationContext或者BeanFactory), 你应该会或者你愿意会使用Spring的aop FactoryBean(记住,factory bean引入了一个间接层, 它能建立不一样类型的对象).

在spring中建立AOP proxy的基本途径是使用org.springframework.aop.framework.ProxyFactoryBean. 这样能够对pointcut和advice做精确控制。可是若是你不须要这种控制,那些简单的选择可能更适合你。

5.5.1. 基本概要

ProxyFactoryBean,和其余Spring的 FactoryBean实现同样,引入一个间接的层次。若是你 定义一个名字为foo的ProxyFactoryBean, 引用foo的对象所看到的不是ProxyFactoryBean 实例自己,而是由实现ProxyFactoryBean的类的 getObject()方法所建立的对象。这个方法将建立一个包装了目标对象 的AOP代理。

使用ProxyFactoryBean或者其余IoC可知的类来建立AOP代理 的最重要的优势之一是IoC能够管理通知和切入点。这是一个很是的强大的功能,可以实 现其余AOP框架很难实现的特定的方法。例如,一个通知自己能够引用应用对象(除了目标对象, 它在任何AOP框架中均可以引用应用对象),这彻底得益于依赖注入所提供的可插入性。

5.5.2. JavaBean的属性

相似于Spring提供的绝大部分FactoryBean实现同样, ProxyFactoryBean也是一个javabean,咱们能够利用它的属性来:

  • 指定你将要代理的目标

  • 指定是否使用CGLIB

一些关键属性来自org.springframework.aop.framework.ProxyConfig :它是全部AOP代理工厂的父类。这些关键属性包括:

  • proxyTargetClass: 若是咱们应该代理目标类, 而不是接口,这个属性的值为true。若是这是true,咱们须要使用CGLIB。

  • optimize: 是否使用强优化来建立代理。不要使用 这个设置,除非你了解相关的AOP代理是如何处理优化的。目前这只对CGLIB代理有效;对JDK 动态代理无效(默认)。

  • frozen: 是否禁止通知的改变,一旦代理工厂已经配置。 默认是false。

  • exposeProxy: 当前代理是否要暴露在ThreadLocal中, 以便它能够被目标对象访问。(它能够经过MethodInvocation获得,不须要ThreadLocal)。 若是一个目标须要得到它的代理而且exposeProxy的值是ture,可使用 AopContext.currentProxy()方法。

  • aopProxyFactory: 所使用的AopProxyFactory具体实现。 这个参数提供了一条途径来定义是否使用动态代理、CGLIB仍是其余代理策略。默认实现将适当地选择动态 代理或CGLIB。通常不须要使用这个属性;它的意图是容许Spring 1.1使用另外新的代理类型。

其余ProxyFactoryBean特定的属性包括:

  • proxyInterfaces: 接口名称的字符串数组。若是这个 没有提供,CGLIB代理将被用于目标类。

  • interceptorNames: Advisor、interceptor或其余 被应用的通知名称的字符串数组。顺序是很重要的。这里的名称是当前工厂中bean的名称,包 括来自祖先工厂的bean的名称。

  • singleton: 工厂是否返回一个单独的对象,不管 getObject()被调用多少次。许多FactoryBean 的实现提供这个方法。默认值是true。若是你想要使用有状态的通知--例如,用于有状态的 mixin--将这个值设为false,使用prototype通知。

5.5.3. 代理接口

让咱们来看一个简单的ProxyFactoryBean的实际例子。这个例子涉及到 :

  • 一个将被代理的目标bean,在这个例子里,这个bean的被定义为"personTarget".

  • 一个advisor和一个interceptor来提供advice.

  • 一个AOP代理bean定义,该bean指定目标对象(这里是personTarget bean), 代理接口,和使用的advice.

<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.NopInterceptor">
</bean>

<bean id="person" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>

    <property name="target"><ref local="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

请注意:person bean的interceptorNames属性提供一个String列表, 列出的是该ProxyFactoryBean使用的,在当前bean工厂定义的interceptor或者advisor的 名字(advisor,interceptor,before,after returning,和throws advice 对象皆可)。 Advisor在该列表中的次序很重要。

你也许会对该列表为何不采用bean的引用存有疑问。 缘由就在于若是ProxyFactoryBean的singleton属性被设置为false, 那么bean工厂必须能返回多个独立的代理实例。 若是有任何一个advisor自己是prototype的,那么它就须要返回独立的实例, 也就是有必要从bean工厂获取advisor的不一样实例,bean的引用在这里显然是不够的。

上面定义的“person”bean定义能够做为Person接口的实现来使用,以下所示:

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

在同一个IoC的上下文中,其余的bean能够依赖于Person接口,就象依赖于一个普通的java对象同样。

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

在这个例子里,PersonUser类暴露了一个类型为Person的属性。 只要是在用到该属性的地方,AOP代理都能透明的替代一个真实的Person实现。 可是,这个类多是一个动态代理类。也就是有可能把它类型转换为一个Advised接口 (该接口在下面的章节中论述) 。

5.5.4. 代理类

若是你须要代理的是类,而不是一个或多个接口,又该怎么办呢?

想象一下咱们上面的例子,若是没有Person接口, 咱们须要通知一个叫Person的类, 并且该类没有实现任何业务接口。在这种状况下,你能够配置Spring使用CGLIB代理, 而不是动态代理。你只要在上面的ProxyFactoryBean定义中把 它的proxyTargetClass属性改为true就好了。

只要你愿意,即便在有接口的状况下,你也能够强迫Spring使用CGLIB代理。

CGLIB代理是经过在运行期产生目标类的子类来进行工做的。 Spring能够配置这个生成的子类,来代理原始目标类的方法调用。这个子类是用 Decorator设计模式置入到advice中的。

CGLIB代理对于用户来讲应该是透明的。然而,还有如下一些因素须要考虑:

  • Final方法不能被通知,由于不能被重写。

  • 你须要在你的classpath中包括CGLIB的二进制代码,而动态代理对任何JDK都是可用的.

CGLIB和动态代理在性能上有微小的区别,对Spring 1.0来讲,后者稍快。 另外,之后可能会有变化。在这种状况下性能不是决定性因素

 

5.6. 便利的代理建立方式

一般,咱们不须要ProxyFactoryBean的所有功能,由于咱们经常只对一个方面感兴趣: 例如,事务管理。

当咱们仅仅对一个特定的方面干兴趣时,咱们可使用许多便利的工厂来建立AOP代理。这些在其余 章节讨论,因此这里咱们快速浏览一下它们。

5.6.1. TransactionProxyFactoryBean

用Spring提供的jPetStore的示例应用 演示了TransactionProxyFactoryBean的使用方式。

TransactionProxyFactoryBean是ProxyConfig的子类, 所以基本配置信息是和ProxyFactoryBean共享的。 (见上面ProxyConfig的属性列表。)

下面的代码来自于JPetStore application,演示了ProxyFactoryBean是如何工做的。 跟ProxyFactoryBean同样,存在一个目标bean的定义。 类的依赖关系定义在代理工厂bean定义中(petStore),而不是普通Java对象(petStoreTarget)。

TransactionProxyFactoryBean须要设置一个target属性, 还须要设置“transactionAttributes”, “transactionAttributes”用来指定须要事务化 处理的方法,还有要求的传播方式和其余设置:

<bean id="petStoreTarget" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
    <property name="accountDao"><ref bean="accountDao"/></property>
    <!-- Other dependencies omitted -->
</bean>

<bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref local="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

TransactionProxyFactoryBean自动建立一个事务advisor, 该advisor包括一个基于事务属性的切入点。所以只有事务性的方法被通知。

TransactionProxyFactoryBean使用preInterceptors和 postInterceptors属性指定“pre”和“post”通知。它们将拦截器,通知和Advisor数组放置 在事务拦截器先后的拦截器链中。使用XML格式的bean定义中的<list>元素定义,就 象下面同样:

<property name="preInterceptors">
    <list>
        <ref local="authorizationInterceptor"/>
        <ref local="notificationBeforeAdvice"/>
    </list>
</property>
<property name="postInterceptors">
    <list>
        <ref local="myAdvisor"/>
    </list>
</property>

这些属性能够加到上面的“petStore”的bean定义里。一个通用用法是将事务和声明式 安全组合在一块儿使用:一个和EJB提供的相似的方法。

由于使用前拦截器和后拦截器时,用的是真正的实例引用,而不象在 ProxyFactoryBean中用的bean的名字,所以它们只能用于共享实例的通知。 所以它们不能用在有状态的通知中:例如,在mixin中。这和TransactionProxyFactoryBean的要求是 一致的。若是你须要更复杂的,能够定制的AOP,你能够考虑使用普通的ProxyFactoryBean, 或者是自动代理生成器(参考下面)。

尤为是若是咱们将Spring的AOP在许多状况下当作是EJB的替代品,咱们会发现大多数通知是很普通的, 可使用共享实例。声明式的事务管理和安全检查是一个典型的例子。

TransactionProxyFactoryBean依赖于由它的transactionManager 属性指定的TransactionManager。 这种事务管理方式是可插拔的,基于JTA,JDBC或者其余事务管理策略皆可。 这与Spring的事务抽象层有关,而不在于AOP自己。咱们将在下一章中讨论事务机制。

若是你只对声明性事务管理感兴趣,TransactionProxyFactoryBean是一个不错的解决办法, 而且比直接使用ProxyFactoryBean来得简单.

5.6.2. EJB 代理

其它有一些专门的代理用于建立EJB代理,使得EJB的“业务方法”的接口能够被调用代码直接使用。 调用代码并不须要进行JNDI查找或使用EJB的建立方法:这是在可读性和架构灵活性方面的重大提升。

进一步请参考本手册内的Spring的EJB业务。

 

5.7. 使用ProxyFactory以编程的方式建立AOP代理

使用Spring以编程的方式建立AOP代理也很简单。 这使得你不须要Spring的IoC就可以使用Spring的AOP。

下面的代码显示的用拦截器和advisor为目标对象建立代理。目标对象实现的接口将自动被代理:

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

第一步是建立类型为org.springframework.aop.framework.ProxyFactory 的对象。你能够和上面的例子同样用目标对象建立,或者在另外一个构造函数中指定要被代理的接口。

你能够添加拦截器或advisor,在整个ProxyFactory的生命周期内操做它们。若是你添加 IntroductionInterceptionAroundAdvisor,你可使代理实现附加接口。

ProxyFactory(它是从AdvisedSupport继承而来)也提供了一些实用方法,使你能够添加 其它通知类型,好比before通知和throws通知。AdvisedSupport是ProxyFactory和ProxyFactoryBean 的父类。

将AOP代理的建立和IoC框架结合起来在大多数应用中都是最好的实现方式。咱们推荐你和通常状况同样, 不要将AOP配置信息放在Java代码里。

 

5.8. 操做被通知对象

不管你怎么建立AOP代理,你均可以使用org.springframework.aop.framework.Advised 接口来操做它们。任何AOP代理不管实现其它什么接口,均可以类型转换为这个接口。这个接口包括下列方法:

void addInterceptor(Interceptor interceptor) throws AopConfigException;

void addInterceptor(int pos, Interceptor interceptor) 
        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。 若是你添加一个Advisor,使用当前索引返回的advisor就是你添加的对象。若是你添加拦截器或其它通知类型, Spring将当前对象和一个知足要求的切入点封装在一个advisor里。所以,若是你添加 MethodInterceptor, 使用当前索引返回的advisor是一个DefaultPointcutAdvisor,这个advisor返回 MethodInterceptor和知足全部类和方法的切入点。

addAdvisor()被用来添加Advisor。一般会是一个普通的 DefaultPointcutAdvisor,它能够和任何通知或切入点(除了引用)一块儿使用。

缺省状况下,在每次代理被建立的时候添加或删除advisor或拦截器。惟一的限制是不能添加或删除 引入advisor,由于工厂提供的已存在的代理不反映接口的变化。(你能够从工厂获得一个新的代理来避免这个问题)

是否建议在产品中修改业务对象的通知还值得怀疑,虽然毫无疑问存在合理的使用状况。可是, 在开发中这是很是有用的:例如,在测试中。我有时候发现以拦截器或其它通知的形式来添加测试代码很是有用, 这样就能够进入我想要测试的方法调用。(例如,通知能够进入为这个方法建立的事务中: 在为回滚事务做标记前,运行SQL检查数据库是否被正确更新。)

根据你建立代理的方式,你一般能够设置frozen标记,这样Advised 的isFrozen()就返回true,任何添加或删除通知都将致使 AopConfigException。这种冻结被通知对象状态的方法在一些状况下是很是有用的: 例如,为了阻止调用代码删除一个安全拦截器。若是已知运行时修改通知不被容许,这还能够被Spring 1.1用来 做优化。

 

5.9. 使用“autoproxy”功能

目前为止,咱们已经讨论了使用ProxyFactoryBean或相似的工厂bean来显式建立AOP代理。

Spring也容许咱们使用“autoproxy”的bean定义,它能够自动代理所选择的bean定义。这是创建在Spring的 “bean后处理器”机制上的,它可以在容器载入bean定义的时候修改任何bean定义。

在这个模型中,你能够在你的XML bean定义文件中创建特殊的bean定义,来配置自动代理机制。这容许你声明目标对象以使用自动代理功能: 你就能够不须要使用ProxyFactoryBean。

有两种方法来实现自动代理:

  • 使用一个自动代理生成器,它引用当前上下文中的那些特殊bean

  • 有一个特殊的自动代理建立的状况值得单独考虑:由源代码级元数据驱动的自动代理建立

5.9.1. 自动代理的bean定义

org.springframework.aop.framework.autoproxy包提供了下列标准自动代理生成器。

5.9.1.1. BeanNameAutoProxyCreator

BeanNameAutoProxyCreator为名字符合某个值或统配符的bean自动建立AOP代理。

<bean id="jdkBeanNameProxyCreator" 
    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 属性,而不是一个拦截器列表,这个属性容许为prototype的advisor提供正确的行为。虽然名字叫 “拦截器”,可是也能够是advisor或任何通知类型。

就象通常的自动代理建立同样,使用BeanNameAutoProxyCreator的主要目的 是对多个对象使用相同的配置信息,而且减小配置的工做量。这在为多个对象使用声明式事务时是一个很流行的选择。

在上面的例子中,名字匹配的bean定义,如“jdkMyBean”和“onlyJdk”,是包含目标类的普通bean定义。 BeanNameAutoProxyCreator将自动建立AOP代理。相同的通知会被因用到全部匹配的bean。 注意,若是使用了advisor(而不是上面例子中的拦截器),切入点可能对不一样的bean会不一样。

5.9.1.2. DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator是一个更通用,更强大的自动代理生成器。它将 自动应用于当前上下文的符合条件的advisor,而不须要在自动代理advisor的bean定义中包含特定的bean名字。 它有助于配置的一致性,并避免象BeanNameAutoProxyCreator同样重复配置。

使用这个机制包括:

  • 指定一个DefaultAdvisorAutoProxyCreator的bean定义

  • 在相同或相关上下文中指定任何数目的Advisor。注意这些必须是Advisor, 而不只仅是拦截器或其它通知。这是很必要的,由于必须有一个切入点来检查每一个通知是否符合候选bean定义。

DefaultAdvisorAutoProxyCreator会自动计算每一个advisor包含的的切入点,看看 是否有什么通知应该被引用到每一个业务对象(好比例子中的“businessObject1”和“businessObject2”)。

这意味着任何数目的advisor均可以自动应用到每一个业务对象。若是advisor中没有任何切入点符合业务对象的 方法,这个对象就不会被代理。由于会为新的业务对象添加bean定义,若是必要,它们会自动被代理。

通常来讲,自动代理能够保证调用者或依赖没法接触未被通知的对象。在这个ApplicationContext上 调用getBean("businessObject1")返回一个AOP代理,而不是目标业务对象。

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

<bean id="txAdvisor"
    autowire="constructor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="order"><value>1</value></property>
</bean>

<bean id="customAdvisor"
    class="com.mycompany.MyAdvisor">
</bean>

<bean id="businessObject1"
    class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2"
    class="com.mycompany.BusinessObject2">
</bean>

若是你想在几个业务对象上应用相同的通知,DefaultAdvisorAutoProxyCreator 就很是有用。一旦定义恰当,你能够简单地添加业务对象而不须要包括特定的代理配置。你也能够很是容易地 删除所附加的方面--例如,跟踪或性能监控的方面--能够尽量减小配置修改。

DefaultAdvisorAutoProxyCreator支持过滤(使用命名规则以便只计算某一些 advisor,容许在一个工厂中使用多个,被不一样配置的AdvisorAutoProxyCreator)和排序。Advisor能够实现 org.springframework.core.Ordered接口以保证正确的排序,若是排序确实须要。在 上面的例子中,TransactionAttributeSourceAdvisor有一个可配置的顺序值,缺损是不排序。

5.9.1.3. AbstractAdvisorAutoProxyCreator

这是DefaultAdvisorAutoProxyCreator的父类。你能够继承它实现你本身的自动代理生成器,这种状况不太常见, 通常是advisor定义不能给DefaultAdvisorAutoProxyCreator框架的行为提供足够的定制。

5.9.2. 使用元数据驱动的自动代理

一种特别重要的自动代理类型是由元数据驱动的。这和.NET的ServicedComponents编程框架 很是相似。它没有象EJB那样使用XML部署描述,事务管理和其它企业级业务的配置都是定义在源代码级的属性上。

在这种状况下,你可使用DefaultAdvisorAutoProxyCreator,以及能够读取元数据属性的 Advisor。元数据细节定义在候选advisor的切入点部分,而不是自动代理建立类自己。

这是DefaultAdvisorAutoProxyCreator的一种特殊状况,可是它自己而言是值得考虑的。 (能够读取元数据的代码处于advisor的切入点中,而不是AOP框架自己。)

jPetStore示例应用的/attributes目录演示了属性驱动的自动代理的使用。在这个例子中, 没有必要使用TransactionProxyFactoryBean。仅仅在业务对象上定义业务属性就足够了,由于 使用了可知元数据的切入点。bean定义在/WEB-INF/declarativeServices.xml中,包括下 面的代码。注意这是通用的,能够在jPetStore之外的地方使用:

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

<bean id="transactionAttributeSource"
    class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"
    autowire="constructor">
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor"
    autowire="byType">
</bean>

<bean id="transactionAdvisor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"
    autowire="constructor" >
</bean>

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

DefaultAdvisorAutoProxyCreator bean定义--在这种状况下称做“advisor”,可是名字 可有可无--会在当前的应用上午中选择全部符合的切入点。在这个例子中,类型为 TransactionAttributeSourceAdvisor的“transactionAdvisor”bean定义将会应用于包含事务属性 的类或方法。TransactionAttributeSourceAdvisor经过构造函数依赖于TransactionInterceptor。这个例子经过自动 装配来解析它。AttributesTransactionAttributeSource依赖于 org.springframework.metadata.Attributes接口的一个实现。在这段代码中,“attributes” bean使用Jakarta Commons Attributes API来获取属性信息。(应用代码必须使用Commons Attributes编译任务编译。)

这里定义的TransactionInterceptor依赖于一个 PlatformTransactionManager定义,它并无被包括在这个通用的文件中(虽然应该是这样), 这是由于它是和应用的事务需求相关的(通常地,是想这个例子中的JTA,或者Hibernate,JDO 或JDBC):

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>
若是你只要求声明式事务管理,使用这些通用的XML定义就可使得Spring自动代理含有事务属性的全部类和方法。 你不须要直接和AOP打交道,而且编程模型和.NET的ServicedComponents很是类似。

这个机制具备可扩展性。它能够基于定制的属性来使用自动代理。你须要:

  • 定义你的定制属性。

  • 指定的Advisor包含必要的通知和由方法或类的定制属性所触发的切入点。你可使用已经存在的通知,仅仅实 现用来选择定制属性的切入点。

这些advisor可能对每一个被通知类都是惟一的(例如,maxin)。它们仅仅须要被定义成 prototype bean,而不是singleton bean。例如,Spring的测试套件中的LockMixin 引入拦截器能够和一个属性驱动切入点一块儿来定位一个maxin,就象这里演示的。咱们使用JavaBean配置的 普通的DefaultPointcutAdvisor:

<bean id="lockMixin"
    class="org.springframework.aop.LockMixin"
    singleton="false"
/>

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

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

若是知道属性的切入点符合anyBean或者其它bean定义中的任何方法,这个maxin 将被应用。注意,lockMixin和lockableAdvisor定义都是 prototype的。myAttributeAwarePointcut切入点能够被定义成singleton,由于它不为 不一样的被通知对象保存状态。

 

5.10. 使用TargetSources

Spring提供了TargetSource的概念,由 org.springframework.aop.TargetSource接口定义。这个接口负责返回实现切入点的 “目标对象”。每次AOP代理处理方法调用时,目标实例都会用到TargetSource实现。

使用Spring AOP的开发者通常不须要直接使用TargetSources,可是这提供了一种强大的方法来支持池,热交换, 和其它复杂目标。例如,一个支持池的TargetSource能够在每次调用时返回不一样的目标对象实例,使用池来管理实例。

若是你没有指定TargetSource,就使用缺省的实现,它封装了一个本地对象。每次调用会返回相同的目标对象 (和你指望的同样)。

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

当使用定制目标源时,你的目标一般须要定义为prototype bean,而不是singleton bean。这使得 Spring在须要的时候建立一个新的目标实例。

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

<bean id="swapper" 
    class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg><ref local="initialTarget"/></constructor-arg>
</bean>

<bean id="swappable" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource">
        <ref local="swapper"/>
    </property>
</bean>

上面的swap()调用会修改swappable这个bean的目标。持有对这个bean应用的客户端 将不会知道这个变化,但会马上转为使用新的目标对象。

虽然这个例子没有添加任何通知--使用TargetSource也不必添加通知-- 固然任何TargetSource均可以和任何一种通知一块儿使用。

5.10.2. 支持池的目标源

使用支持池的目标源提供了一种和无状态的session EJB相似的编程模式,在无状态的session EJB中,维护了 一个相同实例的池,提供从池中获取可用对象的方法。

Spring的池和SLSB的池之间的重要区别在于Spring的池能够被应用到任何普通Java对象。就象Spring的通用 的作法,这个业务也能够以非侵入的方式被应用。

Spring直接支持Jakarta Commons Pool 1.1,它是一种很是高效的池实现。使用这个功能,你须要在你的应用的 classpath中添加commons-pool的Jar文件。也能够直接继承 org.springframework.aop.target.AbstractPoolingTargetSource来支持其它池API。

下面是一个配置的例子:

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" 
    singleton="false">
    ... properties omitted
</bean>

<bean id="poolTargetSource" 
    class="org.springframework.aop.target.CommonsPoolTargetSource">
    <property name="targetBeanName"><value>businessObject</value></property>
    <property name="maxSize"><value>25</value></property>
</bean>

<bean id="businessObject" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource"><ref local="poolTargetSource"/></property>
    <property name="interceptorNames"><value>myInterceptor</value></property>
</bean>

注意例子中的目标对象“businessObjectTarget”必须是prototype。这样在 PoolingTargetSource的实如今扩大池容量的时候能够建立目标的新实例。关于这些属性的 信息能够参考AbstractPoolingTargetSource和子类的Javadoc。maxSize是最基本的属性, 被保证老是存在。

在这种状况下,名字为“myInterceptor”的拦截器须要定义在同一个IoC上下文中。可是,并不必定须要 指定拦截器也用池。若是你仅须要池,而且没有其它通知,能够根本不设置属性interceptorNames。

也能够配置Spring以即可以将任何池化的对象转换类型为 org.springframework.aop.target.PoolingConfig接口。经过这个接口的一个引入,能够获得 配置信息和池的当前大小。你须要这样定义一个advisor:

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

经过调用AbstractPoolingTargetSource类上的方法,能够获得这个advisor, 所以使用MethodInvokingFactoryBean。这个advisor的名字(“poolConfigAdvisor”)必须在暴露池化对象的 This advisor is obtained by calling a convenience method on the ProxyFactoryBean中的拦截器名字列表中。

这个类型转换就象下面:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
池化无状态业务对象并非老是必要的。咱们不认为这是缺省选择,由于大多数无状态对象天然就是线程 安全的,若是资源被缓存,实例池化会有问题。

简单的池也可使用自动代理。任何自动代理生成器均可以设置TargetSources。

5.10.3. Prototype目标源

设置“prototype”目标源和支持池的目标源相似。在每次方法调用的时候都会建立一个新的目标实例。 虽然在现代JVM中建立对象的代价不是很高,可是装配新对象的代价可能更高(为了maz知足它的IoC依赖关系)。 所以没有好的理由不该该使用这个方法。

为了这么作,你能够修改上面的的poolTargetSource定义,就向下面同样。 (为了清晰起见,我修改了名字。)

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

只有一个属性:目标bean的名字。在TargetSource实现中使用继承是为了保证命名的一致性。就象支持池的 目标源同样,目标bean必须是一个prototype的bean定义。

 

5.11. 定义新的通知类型

Spring AOP设计可以很容易地扩展。虽然拦截实现的策略目前只在内部使用,但仍是有可能支持拦截around通知, before通知,throws通知和after returning通知之外的任何通知类型。

org.springframework.aop.framework.adapter 包是一个支持添加新的定制通知类型而不修改核心框架的SPI(译:多是API)包。定制通知类型的惟一限制是它必须实现 org.aopalliance.aop.Advice标记接口。

更多信息请参考org.springframework.aop.framework.adapter包的Javadoc。

 

5.12. 进一步的资料和资源

对于AOP的介绍,我推荐Ramnivas Laddad (Manning, 2003)写的AspectJ in Action

进一步的Spring AOP的例子请参考Spring的示例应用:

  • JPetStore的缺省配置演示了使用TransactionProxyFactoryBean来定义声明式事务管理。

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

若是你对Spring AOP更多高级功能感兴趣,能够看一下测试套件。测试覆盖率超过90%,而且演示了本文档没有提到 的许多高级功能。

 

5.13. 路标

Spring AOP,就象Spring的其它部分,是开发很是活跃的部分。核心API已经稳定了。象Spring的其它部分同样, AOP框架是很是模块化的,在保留基础设计的同时提供扩展。在Spring 1.1到1.2阶段有不少地方可能会有所提升,可是这 些地方也保留了向后兼容性。它们是:

  • 性能的提升:AOP代理的建立由工厂经过策略接口处理。所以咱们可以支持额外的AOP 代理类型而不影响用户代码或核心实现。对于Spring 1.1,咱们正在检查AOP代理实现的全部字节码,万一不须要 运行时通知改变。这应该大大减小AOP框架的额外操做。可是注意,AOP框架的额外操做不是在普通使用中须要考虑 的内容。

  • 更具表达力的切入点:Spring目前提供了一个具备表达力的切入点接口,可是咱们 添加更多的切入点实现。咱们正在考虑提供一个简单但具备强大表达式语言的实现。若是你但愿贡献一个有用的 切入点实现,咱们将很是欢迎。

  • 引入方面这个高层概念,它包含多个advisor。

相关文章
相关标签/搜索