首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

第 五 章 Spring AOP: Spring之面向方面编程

2012-10-07 
第 5 章 Spring AOP: Spring之面向方面编程第 5 章 Spring AOP: Spring之面向方面编程5.1. 概念面向方面编

第 5 章 Spring AOP: Spring之面向方面编程
第 5 章 Spring AOP: Spring之面向方面编程
5.1. 概念
面向方面编程 (AOP) 提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。 面向对象将应用程序分解成 各个层次的对象,而AOP将程序分解成各个方面 或者说 关注点 。 这使得可以模块化诸如事务管理等这些横切多个对象的关注点。(这些关注点术语称作 横切关注点。)

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

AOP在Spring中的使用:

提供声明式企业服务,特别是作为EJB声明式服务的替代品。这些服务中最重要的是 声明式事务管理,这个服务建立在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"
   
    + 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"
    /></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"
   
   
    >
</bean>

<bean id="attributes"
   
    ...
如果知道属性的切入点符合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"
   
   
    singleton="false">
    ... properties omitted
</bean>

<bean id="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。

热点排行