【转】Spring注解-AOP

FROM:http://www.blogjava.net/supercrsky/articles/174368.htmlhtml

使用Spring进行面向切面编程(AOP)

简介

面向切面编程AOP)提供另一种角度来思考程序结构,经过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)之外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。 (这些关注点术语一般称做 横切(crosscutting) 关注点。)java

Spring的一个关键的组件就是 AOP框架。 尽管如此,Spring IoC容器并不依赖于AOP,这意味着你能够自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善。程序员

Spring中所使用的AOP:express

  • 提供声明式企业服务,特别是为了替代EJB声明式服务。 最重要的服务是 声明性事务管理(declarative transaction management) , 这个服务创建在Spring的抽象事务管理(transaction abstraction)之上。编程

  • 容许用户实现自定义的切面,用AOP来完善OOP的使用。api

这样你能够把Spring AOP看做是对Spring的一种加强,它使得Spring能够不须要EJB就能提供声明式事务管理;或者也可使用Spring AOP框架的所有功能来实现自定义的切面。数组

本章首先 介绍了AOP的概念,不管你打算采用哪一种风格的切面声明,这个部分都值得你一读。 本章剩下的部分将着重于Spring 2.0对AOP的支持; 下一章 提供了关于Spring 1.2风格的AOP概述,也许你已经在其余书本,文章以及已有的应用程序中碰到过这种AOP风格。

若是你只打算使用通用的声明式服务或者预先打包的声明式中间件服务,例如缓冲池(pooling), 那么你没必要不直接使用Spring AOP,而本章的大部份内容也能够直接跳过。  6.1.1. AOP概念

首先让咱们从定义一些重要的AOP概念开始。这些术语不是Spring特有的。 不幸的是,Spring术语并非特别的直观;若是Spring使用本身的术语,将会变得更加使人困惑。

  • 切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。

  • 链接点(Joinpoint): 在程序执行过程当中某个特定的点,好比某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个链接点 老是 表明一个方法的执行。 经过声明一个 org.aspectj.lang.JoinPoint 类型的参数可使通知(Advice)的主体部分得到链接点信息。

  • 通知(Advice): 在切面的某个特定的链接点(Joinpoint)上执行的动做。通知有各类类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器作通知模型,并维护一个以链接点为中心的拦截器链。

  • 切入点(Pointcut): 匹配链接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在知足这个切入点的链接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和链接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring容许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可使用一个引入来使bean实现IsModified 接口,以便简化缓存机制。

  • 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫作 被通知(advised) 对象。 既然Spring AOP是经过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

  • AOP代理(AOP Proxy): AOP框架建立的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理能够是JDK动态代理或者CGLIB代理。 注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来讲,代理的建立是透明的。

  • 织入(Weaving): 把切面(aspect)链接到其它的应用程序类型或者对象上,并建立一个被通知(advised)的对象。 这些能够在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其余纯Java AOP框架同样,在运行时完成织入。

通知的类型:

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

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

  • 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。

  • 后通知(After (finally) advice): 当某链接点退出的时候执行的通知(不管是正常返回仍是异常退出)。

  • 环绕通知(Around Advice): 包围一个链接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知能够在方法调用先后完成自定义的行为。它也会选择是否继续执行链接点或直接返回它们本身的返回值或抛出异常来结束执行。

环绕通知是最经常使用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

跟AspectJ同样,Spring提供全部类型的通知,咱们推荐你使用尽可能简单的通知类型来实现须要的功能。 例如,若是你只是须要用一个方法的返回值来更新缓存,虽然使用环绕通知也能完成一样的事情, 可是你最好使用After returning通知而不是环绕通知。 用最合适的通知类型可使得编程模型变得简单,而且可以避免不少潜在的错误。 好比,你不须要调用 JoinPoint(用于Around Advice)的proceed() 方法,就不会有调用的问题。

在Spring 2.0中,全部的通知参数都是静态类型,所以你可使用合适的类型(例如一个方法执行后的返回值类型)做为通知的参数而不是使用一个对象数组。

切入点(pointcut)和链接点(join point)匹配的概念是AOP的关键,这使得AOP不一样于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知能够被应用到一组横跨多个对象中的方法上(例如服务层的全部业务操做)。

6.1.2. Spring AOP的功能和目标

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

Spring目前仅支持使用方法调用做为链接点(join point)(在Spring bean上通知方法的执行)。 虽然能够在不影响到Spring AOP核心API的状况下加入对成员变量拦截器支持,但Spring并无实现成员变量拦截器。 若是你须要把对成员变量的访问和更新也做为通知的链接点,能够考虑其它语法的Java语言,例如AspectJ。

Spring实现AOP的方法跟其余的框架不一样。Spring并非要尝试提供最完整的AOP实现(尽管Spring AOP有这个能力), 相反的,它其实侧重于提供一种AOP实现和Spring IoC容器的整合,用于帮助解决在企业级开发中的常见问题。

所以,Spring AOP一般都和Spring IoC容器一块儿使用。 Aspect使用普通的bean定义语法(尽管Spring提供了强大的“自动代理(autoproxying)”功能): 与其余AOP实现相比这是一个显著的区别。有些事使用Spring AOP是没法轻松或者高效的完成的,好比说通知一个细粒度的对象。 这种时候,使用AspectJ是最好的选择。不过经验告诉咱们: 于大多数在J2EE应用中遇到的问题,只要适合AOP来解决的,Spring AOP都没有问题,Spring AOP提供了一个很是好的解决方案。

Spring AOP历来没有打算经过提供一种全面的AOP解决方案来取代AspectJ。 咱们相信不管是基于代理(proxy-based )的框架好比说Spring亦或是full-blown的框架好比说是AspectJ都是颇有价值的,他们之间的关系应该是互补而不是竞争的关系。 Spring 2.0能够无缝的整合Spring AOP,IoC 和AspectJ,使得全部的AOP应用彻底融入基于Spring的应用体系。 这样的集成不会影响Spring AOP API或者AOP Alliance API;Spring AOP保留了向下兼容性。接下来的一章会详细讨论Spring AOP API。

6.1.3. Spring的AOP代理

Spring缺省使用J2SE 动态代理(dynamic proxies)来做为AOP的代理。这样任何接口均可以被代理。

Spring也支持使用CGLIB代理. 对于须要代理类而不是代理接口的时候CGLIB代理是颇有必要的。 若是一个业务对象并无实现一个接口,默认就会使用CGLIB。 此外,面向接口编程 也是一个最佳实践,业务对象一般都会实现一个或多个接口。

此外,还能够强制的使用CGLIB:咱们将会在之后讨论这个问题,解释问什么你会要这么作。

在Spring 2.0以后,Spring可能会提供多种其余类型的AOP代理,包括了完整的生成类。这不会影响到编程模型。

6.2. @AspectJ支持

若是你使用Java 5的话,推荐使用Spring提供的@AspectJ切面支持,经过这种方式声明Spring AOP中使用的切面。 "@AspectJ"使用了Java 5的注解,能够将切面声明为普通的Java类。 AspectJ 5发布的 AspectJ project 中引入了这种@AspectJ风格。 Spring 2.0 使用了和AspectJ 5同样的注解,使用了AspectJ 提供的一个库来作切点(pointcut)解析和匹配。 可是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ 的编译器或者织入器(weaver)。

使用AspectJ的编译器或者织入器(weaver)的话就可使用完整的AspectJ 语言,咱们将在 Section 6.7, “在Spring应用中使用AspectJ” 中讨论这个问题。  6.2.1. 启用@AspectJ支持

为了在Spring配置中使用@AspectJ aspects,你必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。 自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,而且确认通知是否如期进行。

经过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:

<aop:aspectj-autoproxy/>

咱们假使你正在使用 Appendix A, XML Schema-based configuration 所描述的schema支持。 关于如何在aop的命名空间中引入这些标签,请参见 Section A.2.6, “The aop schema”

若是你正在使用DTD,你仍旧能够经过在你的application context中添加以下定义来启用@AspectJ支持:

<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

你须要在你的应用程序的classpath中引入两个AspectJ库:aspectjweaver.jar 和 aspectjrt.jar。 这些库能够在AspectJ的安装包(1.5.1或者以后的版本)中的 lib 目录里找到,或者也能够在Spring依赖库的 lib/aspectj 目录下找到。

6.2.2. 声明一个切面

在启用@AspectJ支持的状况下,在application context中定义的任意带有一个@Aspect切面(拥有 @Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP。 如下例子展现了为了完成一个不是很是有用的切面所须要的最小定义:

下面是在application context中的一个常见的bean定义,这个bean指向一个使用了 @Aspect 注解的bean类:

< bean  id ="myAspect"  class ="org.xyz.NotVeryUsefulAspect" >
    <!--  configure properties of aspect here as normal  -->
</ bean >

下面是 NotVeryUsefulAspect 类定义,使用了 org.aspectj.lang.annotation.Aspect 注解。

 

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public  class NotVeryUsefulAspect  {

}


切面(用 @Aspect 注解的类)和其余类同样有方法和字段定义。他们也可能包括切入点,通知和引入(inter-type)声明。

6.2.3. 声明一个切入点(pointcut)

回想一下,切入点决定了链接点关注的内容,使得咱们能够控制通知什么执行。 Spring AOP只支持Spring bean方法执行链接点。因此你能够把切入点看作是匹配Spring bean上的方法执行。 一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了咱们关注那个方法的执行。 在@AspectJ中,一个切入点实际就是一个普通的方法定义提供的一个签名,而且切入点表达式使用 @Pointcut注解来表示。 这个方法的返回类型必须是 void。 以下的例子定义了一个切入点'transfer',这个切入点匹配了任意名为"transfer"的方法执行:

@Pointcut("execution(* transfer(..))")
     private  void transfer()  {}

切入点表达式,也就是 @Pointcut 注解的值,是正规的AspectJ 5切入点表达式。 若是你想要更多了解AspectJ的 切入点语言,请参见 AspectJ 编程指南(若是要了解基于Java 5的扩展请参阅 AspectJ 5 开发手册) 或者其余人写的关于AspectJ的书,例如Colyer et. al.著的《Eclipse AspectJ》或者Ramnivas Laddad著的《AspectJ in Action》。

6.2.3.1. 支持的切入点指定者

 

Spring AOP 支持在切入点表达式中使用以下的AspectJ切入点指定者:

  • execution - 匹配方法执行的链接点,这是你将会用到的Spring的最主要的切入点指定者。

  • within - 限定匹配特定类型的链接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。

  • this - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。

  • target - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。

  • args - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。

  • @target - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。

  • @args - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。

  • @within - 限定匹配特定的链接点,其中链接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。

  • @annotation - 限定匹配特定的链接点(使用Spring AOP的时候方法的执行),其中链接点的主题有某种给定的注解。

由于Spring AOP限制了链接点必须是方法执行级别的,pointcut designators的讨论也给出了一个定义,这个定义和AspectJ的编程指南中的定义相比显得更加狭窄。 除此以外,AspectJ它自己有基于类型的语义,在执行的链接点'this'和'target'都是指同一个对象,也就是执行方法的对象。 Spring AOP是一个基于代理的系统,而且严格区分代理对象自己(对应于'this')和背后的目标对象(对应于'target')

6.2.3.2. 合并切入点表达式

 

切入点表达式可使用using '&&', '||' 和 '!'来合并.还能够经过名字来指向切入点表达式。 如下的例子展现了三种切入点表达式:anyPublicOperation(在一个方法执行链接点表明了任意public方法的执行时匹配); inTrading(在一个表明了在交易模块中的任意的方法执行时匹配) 和 tradingOperation(在一个表明了在交易模块中的任意的公共方法执行时匹配)。

@Pointcut("execution(public * *(..))")
     private  void anyPublicOperation()  {}

    @Pointcut("within(com.xyz.someapp.trading..*")
     private  void inTrading()  {}

    @Pointcut("anyPublicOperation() && inTrading()")
     private  void tradingOperation()  {}

就上所示的,从更小的命名组件来构建更加复杂的切入点表达式是一种最佳实践。 当用名字来指定切入点时使用的是常见的Java成员可视性访问规则。 (好比说,你能够在同一类型中访问私有的切入点,在继承关系中访问受保护的切入点,能够在任意地方访问公共切入点。 成员可视性访问规则不影响到切入点的 匹配

在AspectJ 1.5.1中有一个bug (#140357)有时候可能会致使Spring所使用的AspectJ切入点解析失败, 即便用一个已命名的切入点来引用到另外一个同类型的切入点的时候。 在AspectJ的开发中已经解决这个bug,能够在AspectJ的下载页面获得。在1.5.2发布时将会包含这一fix。 若是你遇到了这个问题,你能够去下载AspectJ的开发构建包,而且更新你的 aspectjweaver.jar,这是在AspectJ 1.5.2发布前的临时解决方案。

6.2.3.3. 共享常见的切入点(pointcut)定义

 

当开发企业级应用的时候,你一般会想要从几个切面来参考模块化的应用和特定操做的集合。 咱们推荐定义一个“SystemArchitecture”切面来捕捉常见的切入点表达式。一个典型的切面可能看起来像下面这样:

 

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public  class SystemArchitecture  {

  /**
   * A join point is in the web layer if the method is defined
   * in a type in the com.xyz.someapp.web package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  /**
   * A join point is in the service layer if the method is defined
   * in a type in the com.xyz.someapp.service package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  /**
   * A join point is in the data access layer if the method is defined
   * in a type in the com.xyz.someapp.dao package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  /**
   * A business service is the execution of any method defined on a service
   * interface. This definition assumes that interfaces are placed in the
   * "service" package, and that implementation types are in sub-packages.
   *
   * If you group service interfaces by functional area (for example,
   * in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
   * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
   * could be used instead.
   
*/

  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}

  /**
   * A data access operation is the execution of any method defined on a
   * dao interface. This definition assumes that interfaces are placed in the
   * "dao" package, and that implementation types are in sub-packages.
   
*/

  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}


示例中的切入点定义一个你能够在任何须要切入点表达式的地方可引用的切面。好比,为了使service层事务化,你能够写: 

< aop:config >
   < aop:advisor
      
pointcut ="com.xyz.someapp.SystemArchitecture.businessService()"
      advice-ref
="tx-advice" />
</ aop:config >

< tx:advice  id ="tx-advice" >
< tx:attributes >
     < tx:method  name ="*"  propagation ="REQUIRED" />
   </ tx:attributes >
</ tx:advice >



在 Section 6.3, “Schema-based AOP support” 中讨论 <aop:config> 和 <aop:advisor>标签。 在 Chapter 9, 事务管理 中讨论事务标签。

6.2.3.4. 示例

 

Spring AOP 用户可能会常用 execution pointcut designator。执行表达式的格式以下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

除了返回类型模式,名字模式和参数模式之外,全部的部分都是可选的。 返回类型模式决定了方法的返回类型必须依次匹配一个链接点。 你会使用的最频繁的返回类型模式是 *,它表明了匹配任意的返回类型。 一个全称限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可使用 * 通配符做为全部或者部分命名模式。 参数模式稍微有点复杂:() 匹配了一个不接受任何参数的方法, 而 (..) 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 (*) 匹配了一个接受一个任何类型的参数的方法。 模式 (*,String) 匹配了一个接受两个参数的方法,第一个能够是任意类型,第二个则必须是String类型。 请参见AspectJ编程指南的Language Semantics 部分。

下面给出一些常见切入点表达式的例子。

  • 任意公共方法的执行:

    execution(public * *(..))
  • 任何一个以“set”开始的方法的执行:

    execution(* set*(..))
  • AccountService 接口的任意方法的执行:

    execution(* com.xyz.service.AccountService.*(..))
  • 定义在service包里的任意方法的执行:

    execution(* com.xyz.service.*.*(..))
  • 定义在service包或者子包里的任意方法的执行:

    execution(* com.xyz.service..*.*(..))
  • 在service包里的任意链接点(在Spring AOP中只是方法执行) :

    within(com.xyz.service.*)
  • 在service包或者子包里的任意链接点(在Spring AOP中只是方法执行) :

    within(com.xyz.service..*)
  • 实现了 AccountService 接口的代理对象的任意链接点(在Spring AOP中只是方法执行) :

    this(com.xyz.service.AccountService)
    'this'在binding form中用的更多:- 请常见如下讨论通知的章节中关于如何使得代理对象能够在通知体内访问到的部分。
  • 实现了 AccountService 接口的目标对象的任意链接点(在Spring AOP中只是方法执行) :

    target(com.xyz.service.AccountService)
    'target'在binding form中用的更多:- 请常见如下讨论通知的章节中关于如何使得目标对象能够在通知体内访问到的部分。
  • 任何一个只接受一个参数,且在运行时传入的参数实现了 Serializable 接口的链接点 (在Spring AOP中只是方法执行)

    args(java.io.Serializable)
    'args'在binding form中用的更多:- 请常见如下讨论通知的章节中关于如何使得方法参数能够在通知体内访问到的部分。

    请注意在例子中给出的切入点不一样于 execution(* *(java.io.Serializable)): args只有在动态运行时候传入参数是可序列化的(Serializable)才匹配,而execution 在传入参数的签名声明的类型实现了 Serializable 接口时候匹配。

  • 有一个 @Transactional 注解的目标对象中的任意链接点(在Spring AOP中只是方法执行)

    @target(org.springframework.transaction.annotation.Transactional)
    '@target' 也能够在binding form中使用:请常见如下讨论通知的章节中关于如何使得annotation对象能够在通知体内访问到的部分。
  • 任何一个目标对象声明的类型有一个 @Transactional 注解的链接点(在Spring AOP中只是方法执行)

    @within(org.springframework.transaction.annotation.Transactional)
    '@within'也能够在binding form中使用:- 请常见如下讨论通知的章节中关于如何使得annotation对象能够在通知体内访问到的部分。
  • 任何一个执行的方法有一个 @Transactional annotation的链接点(在Spring AOP中只是方法执行)

    @annotation(org.springframework.transaction.annotation.Transactional)
    '@annotation' 也能够在binding form中使用:- 请常见如下讨论通知的章节中关于如何使得annotation对象能够在通知体内访问到的部分。
  • 任何一个接受一个参数,而且传入的参数在运行时的类型实现了 @Classified annotation的链接点(在Spring AOP中只是方法执行)

    @args(com.xyz.security.Classified)
    '@args'也能够在binding form中使用:- 请常见如下讨论通知的章节中关于如何使得annotation对象能够在通知体内访问到的部分。

6.2.4. 声明通知

 

通知是跟一个切入点表达式关联起来的,而且在切入点匹配的方法执行以前或者以后或者以前和以后运行。 切入点表达式多是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。

6.2.4.1. 前置通知(Before advice)

 

一个切面里使用 @Before 注解声明前置通知:

若是使用一个in-place 的切入点表达式,咱们能够把上面的例子换个写法:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}

6.2.4.2. 返回后通知(After returning advice)

 

返回后通知一般在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
说明:你能够在同一个切面里定义多个通知,或者其余成员。咱们只是在展现如何定义一个简单的通知。这些例子主要的侧重点是正在讨论的问题。

有时候你须要在通知体内获得返回的值。你可使用以 @AfterReturning 接口的形式来绑定返回值:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}

在 returning 属性中使用的名字必须对应于通知方法内的一个参数名。 当一个方法执行返回后,返回值做为相应的参数值传入通知方法。 一个 returning 子句也限制了只能匹配到返回指定类型值的方法。 (在本例子中,返回值是 Object 类,也就是说返回任意类型都会匹配)

6.2.4.3. 抛出后通知(After throwing advice)

 

抛出后通知在一个方法抛出异常后执行。使用 @AfterThrowing 注解来声明:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}

你一般会想要限制通知只在某种特殊的异常被抛出的时候匹配,你还但愿能够在通知体内获得被抛出的异常。 使用 throwing 属性不光能够限制匹配的异常类型(若是你不想限制,请使用 Throwable 做为异常类型),还能够将抛出的异常绑定到通知的一个参数上。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}

在 throwing 属性中使用的名字必须与通知方法内的一个参数对应。 当一个方法因抛出一个异常而停止后,这个异常将会做为那个对应的参数送至通知方法。 throwing 子句也限制了只能匹配到抛出指定异常类型的方法(上面的示例为 DataAccessException)。

6.2.4.4. 后通知(After (finally) advice)

 

不论一个方法是如何结束的,在它结束后(finally)后通知(After (finally) advice)都会运行。 使用 @After 注解来声明。这个通知必须作好处理正常返回和异常返回两种状况。一般用来释放资源。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}

6.2.4.5. 环绕通知(Around Advice)

 

最后一种通知是环绕通知。环绕通知在一个方法执行以前和以后执行。 它使得通知有机会既在一个方法执行以前又在执行以后运行。而且,它能够决定这个方法在何时执行,如何执行,甚至是否执行。 环绕通知常常在在某线程安全的环境下,你须要在一个方法执行以前和以后共享某种状态的时候使用。 请尽可能使用最简单的知足你需求的通知。(好比若是前置通知(before advice)也能够适用的状况下不要使用环绕通知)。

环绕通知使用 @Around 注解来声明。通知的第一个参数必须是 ProceedingJoinPoint 类型。 在通知体内,调用ProceedingJoinPoint 的 proceed() 方法将会致使潜在的链接点方法执行。 proceed 方法也可能会被调用而且传入一个 Object[]对象-该数组将做为方法执行时候的参数。

当传入一个 Object[] 对象的时候,处理的方法与经过AspectJ编译器处理环绕通知略有不一样。 对于使用传统AspectJ语言写的环绕通知来讲,传入参数的数量必须和传递给环绕通知的参数数量匹配(不是后台的链接点接受的参数数量),而且特定顺序的传入参数代替了将要绑定给链接点的原始值(若是你看不懂不用担忧)。 Spring采用的方法更加简单而且更好得和他的基于代理(proxy-based),只匹配执行的语法相适用。 若是你适用AspectJ的编译器和编织器来编译为Spring而写的@AspectJ切面和处理参数,你只须要了解这一区别便可。 有一种方法可让你写出100%兼容Spring AOP和AspectJ的,咱们将会在后续的通知参数(advice parameters)的章节中讨论它。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}

方法的调用者获得的返回值就是环绕通知返回的值。 例如:一个简单的缓存切面,若是缓存中有值,就返回该值,不然调用proceed()方法。 请注意proceed可能在通知体内部被调用一次,许屡次,或者根本不被调用。

6.2.4.6. 通知参数(Advice parameters)

 

Spring 2.0 提供了完整的通知类型 - 这意味着你能够在通知签名中声明所需的参数,(就像在之前的例子中咱们看到的返回值和抛出异常同样)而不老是使用Object[]。 咱们将会看到如何在通知体内访问参数和其余上下文相关的值。首先让咱们看如下如何编写普通的通知以找出正在被通知的方法。

6.2.4.6.1. 访问当前的链接点
 

任何通知方法能够将第一个参数定义为 org.aspectj.lang.JoinPoint 类型 (环绕通知须要定义为 ProceedingJoinPoint 类型的, 它是 JoinPoint 的一个子类。) JoinPoint 接口提供了一系列有用的方法, 好比 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。

6.2.4.6.2. 传递参数给通知(Advice)
 

咱们已经看到了如何绑定返回值或者异常(使用后置通知(after returning)和异常后通知(after throwing advice)。 为了能够在通知(adivce)体内访问参数,你可使用 args 来绑定。 若是在一个参数表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。 可能给出一个例子会更好理解。假使你想要通知(advise)接受某个Account对象做为第一个参数的DAO操做的执行,你想要在通知体内也能访问到account对象,你能够写以下的代码:

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
public void validateAccount(Account account) {
// ...
}

切入点表达式的 args(account,..) 部分有两个目的: 首先它保证了只会匹配那些接受至少一个参数的方法的执行,并且传入的参数必须是 Account 类型的实例, 其次它使得能够在通知体内经过 account 参数来访问那个account参数。

另一个办法是定义一个切入点,这个切入点在匹配某个链接点的时候“提供”了一个Account对象, 而后直接从通知中访问那个命名的切入点。你能够这样写:

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ..
}

若是想要知道更详细的内容,请参阅 AspectJ 编程指南。

代理对象(this)、目标对象(target) 和注解(@within, @target, @annotation, @args)均可以用一种简单格式绑定。 如下的例子展现了如何使用 @Auditable 注解来匹配方法执行,并提取AuditCode。

首先是 @Auditable 注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}

而后是匹配 @Auditable 方法执行的通知:

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && " +
"@annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
6.2.4.6.3. 决定参数名
 

绑定在通知上的参数依赖切入点表达式的匹配名,并借此在(通知(advice)和切入点(pointcut))的方法签名中声明参数名。 参数名 没法 经过Java反射来获取,因此Spring AOP使用以下的策略来决定参数名字:

  1. 若是参数名字已经被用户明确指定,则使用指定的参数名: 通知(advice)和切入点(pointcut)注解有一个额外的"argNames"属性,该属性用来指定所注解的方法的参数名 - 这些参数名在运行时是 能够 访问的。例子以下:

    @Before(
        value="com.xyz.lib.Pointcuts.anyPublicMethod() && " +
        "@annotation(auditable)",
        argNames="auditable")
        public void audit(Auditable auditable) {
        AuditCode code = auditable.value();
        // ...
        }
        
    若是一个@AspectJ切面已经被AspectJ编译器(ajc)编译过了,那么就不须要再添加 argNames 参数了,由于编译器会自动完成这一工做。
  2. 使用 'argNames' 属性有点不那么优雅,因此若是没有指定'argNames' 属性, Spring AOP 会寻找类的debug信息,而且尝试从本地变量表(local variable table)中来决定参数名字。 只要编译的时候使用了debug信息(至少要使用 '-g:vars' ),就可得到这些信息。 使用这个flag编译的结果是: (1)你的代码将可以更加容易的读懂(反向工程), (2)生成的class文件会稍许大一些(不重要的), (3)移除不被使用的本地变量的优化功能将会失效。 换句话说,你在使用这个flag的时候不会遇到任何困难。

  3. 若是不加上debug信息来编译的话,Spring AOP将会尝试推断参数的绑定。 (例如,要是只有一个变量被绑定到切入点表达式(pointcut expression)、通知方法(advice method)将会接受这个参数, 这是显而易见的)。 若是变量的绑定不明确,将会抛出一个 AmbiguousBindingException 异常。

  4. 若是以上全部策略都失败了,将会抛出一个 IllegalArgumentException 异常。

6.2.4.6.4. 处理参数
 

咱们以前提过咱们将会讨论如何编写一个 带参数的 的proceed()调用,使得不论在Spring AOP中仍是在AspectJ都能正常工做。 解决方法是保证通知签名依次绑定方法参数。好比说:

@Around("execution(List<Account> find*(..)) &&" +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern)
throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}

大多数状况下你都会这样绑定(就像上面的例子那样)。

6.2.4.7. 通知(Advice)顺序

 

若是有多个通知想要在同一链接点运行会发生什么?Spring AOP 的执行通知的顺序跟AspectJ的同样。 在“进入”链接点的状况下,最高优先级的通知会先执行(因此上面给出的两个前置通知(before advice)中,优先级高的那个会先执行)。 在“退出”链接点的状况下,最高优先级的通知会最后执行。(因此上面给出的两个前置通知(before advice)中,优先级高的那个会第二个执行)。 对于定义在相同切面的通知,根据声明的顺序来肯定执行顺序。好比下面这个切面:

@Aspect
public class AspectWithMultipleAdviceDeclarations {
@Pointcut("execution(* foo(..))")
public void fooExecution() {}
@Before("fooExecution()")
public void doBeforeOne() {
// ..
}
@Before("fooExecution()")
public void doBeforeTwo() {
// ..
}
@AfterReturning("fooExecution()")
public void doAfterOne() {
// ..
}
@AfterReturning("fooExecution()")
public void doAfterTwo() {
//..
}
}

这样,假使对于任何一个名字为foo的方法的执行, doBeforeOnedoBeforeTwodoAfterOne 和 doAfterTwo 通知方法都须要运行。 执行顺序将按照声明的顺序来肯定。在这个例子中,执行的结果会是:

doBeforeOne
doBeforeTwo
foo
doAfterOne
doAfterTwo

换言之,由于doBeforeOne先定义,它会先于doBeforeTwo执行,而doAfterTwo后于doAfterOne定义,因此它会在doAfterOne以后执行。 只须要记住通知是按照定义的顺序来执行的就能够了。 - 若是想要知道更加详细的内容,请参阅AspectJ编程指南。

当定义在 不一样的 切面里的两个通知都须要在一个相同的链接点中运行,那么除非你指定,不然执行的顺序是未知的。 你能够经过指定优先级来控制执行顺序。在Spring中能够在切面类中实现 org.springframework.core.Ordered 接口作到这一点。 在两个切面中,Ordered.getValue() 方法返回值较低的那个有更高的优先级。

6.2.5. 引入(Introductions)

 

引入(Introductions)(在AspectJ中被称为inter-type声明)使得一个切面能够定义被通知对象实现一个给定的接口,而且能够表明那些对象提供具体实现。

使用 @DeclareParents注解来定义引入。这个注解被用来定义匹配的类型拥有一个新的父亲。 好比,给定一个接口 UsageTracked,而后接口的具体实现 DefaultUsageTracked 类, 接下来的切面声明了全部的service接口的实现都实现了 UsageTracked 接口。(好比为了经过JMX输出统计信息)。

@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+",
defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() &&" +
"this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}

实现的接口经过被注解的字段类型来决定。@DeclareParents 注解的 value 属性是一个AspectJ的类型模式:- 任何匹配类型的bean都会实现 UsageTracked 接口。 请注意,在上面的前置通知(before advice)的例子中,service beans 能够直接用做UsageTracked 接口的实现。 若是须要编程式的来访问一个bean,你能够这样写:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

6.2.6. 切面实例化模型

 
这是一个高级主题...

默认状况下,在application context中每个切面都会有一个实例。 AspectJ 把这个叫作单个实例化模型(singleton instantiation model)。 也能够用其余的生命周期来定义切面:- Spring支持AspectJ的 perthis 和 pertarget 实例化模型 (如今还不支持percflow、percflowbelow 和 pertypewithin )。

一个"perthis" 切面的定义:在 @Aspect 注解中指定perthis 子句。 让咱们先来看一个例子,而后解释它是如何运做的:

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}

这个perthis子句的效果是每一个独立的service对象执行时都会建立一个切面实例(切入点表达式所匹配的链接点上的每个独立的对象都会绑定到'this'上)。 service对象的每一个方法在第一次执行的时候建立切面实例。切面在service对象失效的同时失效。 在切面实例被建立前,全部的通知都不会被执行,一旦切面对象建立完成,定义的通知将会在匹配的链接点上执行,可是只有当service对象是和切面关联的才能够。 若是想要知道更多关于per-clauses的信息,请参阅 AspectJ 编程指南。

'pertarget'实例模型的跟“perthis”彻底同样,只不过是为每一个匹配于链接点的独立目标对象建立一个切面实例。

6.2.7. 例子

 

如今你已经看到了每一个独立的部分是如何运做的了,是时候把他们放到一块儿作一些有用的事情了!

由于乐观锁的关系,有时候business services可能会失败(有人甚至在一开始运行事务的时候就失败了)。若是从新尝试一下,颇有可能就会成功。 对于business services来讲,重试几回是很正常的(Idempotent操做不须要用户参与,不然会得出矛盾的结论) 咱们可能须要透明的重试操做以免让客户看见 OptimisticLockingFailureException 例外被抛出。 很明显,在一个横切多层的状况下,这是很是有必要的,所以经过切面来实现是很理想的。

由于咱们想要重试操做,咱们会须要使用到环绕通知,这样咱们就能够屡次调用proceed()方法。下面是简单的切面实现:

@Aspect
public  class OptimisticOperationExecutor  implements Ordered  {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}

public int getOrder() {
return this.order;
}

public void setOrder(int order) {
this.order = order;
}

@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
OptimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}

catch(OptimisticLockingFailureException ex) {
lockFailureException = ex;
}

}

while(numAttempts <= this.maxRetries);
throw lockFailureException;
}

}

 

请注意切面实现了 Ordered 接口,这样咱们就能够把切面的优先级设定为高于事务通知(咱们每次重试的时候都想要在一个全新的事务中进行)。 maxRetries 和 order 属性均可以在Spring中配置。 主要的动做在 doOptimisticOperation 这个环绕通知中发生。 请注意这个时候咱们全部的 businessService() 方法都会使用这个重试策略。 咱们首先会尝试处理,而后若是咱们获得一个OptimisticLockingFailureException 意外,咱们只须要简单的重试,直到咱们耗尽全部预设的重试次数。

对应的Spring配置以下:

< aop:aspectj-autoproxy />
< bean  id ="optimisticOperationExecutor"
class
="com.xyz.myapp.service.impl.OptimisticOperationExecutor" >
< property  name ="maxRetries"  value ="3" />
< property  name ="order"  value ="100" />
</ bean >

为了改进切面,使之仅仅重试idempotent操做,咱们能够定义一个 Idempotent 注解:

@Retention(RetentionPolicy.RUNTIME)
public @ interface Idempotent  {
// marker annotation
}

而且对service操做的实现进行注解。 这样若是你只但愿改变切面使得idempotent的操做会尝试屡次,你只须要改写切入点表达式,这样只有 @Idempotent 操做会匹配:

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}

6.3. Schema-based AOP support

 

若是你没法使用Java 5,或者你比较喜欢使用XML格式,Spring2.0也提供了使用新的"aop"命名空间来定义一个切面。 和使用@AspectJ风格彻底同样,切入点表达式和通知类型一样获得了支持,所以在这一节中咱们将着重介绍新的 语法 和回顾前面咱们所讨论的如何写一个切入点表达式和通知参数的绑定等等(Section 6.2, “@AspectJ支持”)。

使用本章所介绍的aop命名空间标签(aop namespace tag),你须要引入Appendix A, XML Schema-based configuration中说起的spring-aop schema。 参见Section A.2.6, “The aop schema”

在Spring的配置文件中,全部的切面和通知器都必须定义在 <aop:config> 元素内部。 一个application context能够包含多个<aop:config>。 一个 <aop:config> 能够包含pointcut,advisor和aspect元素,而且三者必须按照这样的顺序进行声明。

6.3.1. 声明一个切面

 

有了schema的支持,切面就和常规的Java对象同样被定义成application context中的一个bean。 对象的字段和方法提供了状态和行为信息,XML文件则提供了切入点和通知信息。

切面使用<aop:aspect>来声明,backing bean(支持bean)经过 ref 属性来引用:

<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

切面的支持bean(上例中的"aBean")能够象其余Spring bean同样被容器管理配置以及依赖注入。

6.3.2. 声明一个切入点

 

切入点能够在切面里面声明,这种状况下切入点只在切面内部可见。切入点也能够直接在<aop:config>下定义,这样就可使多个切面和通知器共享该切入点。

一个描述service层中表示全部service执行的切入点能够以下定义:

<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>

注意切入点表达式自己使用了 Section 6.2, “@AspectJ支持” 中描述的AspectJ 切入点表达式语言。 若是你在Java 5环境下使用基于schema的声明风格,可参考切入点表达式类型中定义的命名式切入点,不过这在JDK1.4及如下版本中是不被支持的(由于依赖于Java 5中的AspectJ反射API)。 因此在JDK 1.5中,上面的切入点的另一种定义形式以下:

<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
</aop:config>

假定你有 Section 6.2.3.3, “共享常见的切入点(pointcut)定义”中说描述的 SystemArchitecture 切面。

在切面里面声明一个切入点和声明一个顶级的切入点很是相似:

<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>

当须要链接子表达式的时候,'&&'在XML中用起来很是不方便,因此关键字'and', 'or' 和 'not'能够分别用来代替'&&', '||' 和 '!'。

注意这种方式定义的切入点经过XML id来查找,而且不能定义切入点参数。在基于schema的定义风格中命名切入点支持较之@AspectJ风格受到了不少的限制。

6.3.3. 声明通知

 

和@AspectJ风格同样,基于schema的风格也支持5种通知类型而且二者具备一样的语义。

6.3.3.1. 通知(Advice)

 

Before通知在匹配方法执行前进入。在<aop:aspect>里面使用<aop:before>元素进行声明。

<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>

这里 dataAccessOperation 是一个顶级(<aop:config>)切入点的id。 要定义内置切入点,可将 pointcut-ref 属性替换为pointcut 属性:

<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>

咱们已经在@AspectJ风格章节中讨论过了,使用命名切入点可以明显的提升代码的可读性。

Method属性标识了提供了通知的主体的方法(doAccessCheck)。这个方法必须定义在包含通知的切面元素所引用的bean中。 在一个数据访问操做执行以前(执行链接点和切入点表达式匹配),切面中的"doAccessCheck"会被调用。

6.3.3.2. 返回后通知(After returning advice)

 

After returning通知在匹配的方法彻底执行后运行。和Before通知同样,能够在<aop:aspect>里面声明。例如:

<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>

和@AspectJ风格同样,通知主体能够接收返回值。使用returning属性来指定接收返回值的参数名:

<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>

doAccessCheck方法必须声明一个名字叫 retVal 的参数。 参数的类型强制匹配,和先前咱们在@AfterReturning中讲到的同样。例如,方法签名能够这样声明:

public void doAccessCheck(Object retVal) {...

6.3.3.3. 抛出异常后通知(After throwing advice)

 

After throwing通知在匹配方法抛出异常退出时执行。在 <aop:aspect> 中使用after-throwing元素来声明:

<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>

和@AspectJ风格同样,能够从通知体中获取抛出的异常。 使用throwing属性来指定异常的名称,用这个名称来获取异常:

<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
thowing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>

doRecoveryActions方法必须声明一个名字为 dataAccessEx 的参数。 参数的类型强制匹配,和先前咱们在@AfterThrowing中讲到的同样。例如:方法签名能够以下这般声明:

public void doRecoveryActions(DataAccessException dataAccessEx) {...

6.3.3.4. 后通知(After (finally) advice)

 

After (finally)通知在匹配方法退出后执行。使用 after 元素来声明:

<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>

6.3.3.5. 通知

 

Around通知是最后一种通知类型。Around通知在匹配方法运行期的“周围”执行。 它有机会在目标方法的前面和后面执行,并决定何时运行,怎么运行,甚至是否运行。 Around通知常常在须要在一个方法执行前或后共享状态信息,而且是线程安全的状况下使用(启动和中止一个计时器就是一个例子)。 注意选择能知足你需求的最简单的通知类型(i.e.若是简单的before通知就能作的事情绝对不要使用around通知)。

Around通知使用 aop:around 元素来声明。 通知方法的第一个参数的类型必须是 ProceedingJoinPoint 类型。 在通知的主体中,调用 ProceedingJoinPointproceed() 方法来执行真正的方法。 proceed 方法也可能会被调用而且传入一个 Object[] 对象 - 该数组将做为方法执行时候的参数。 参见 Section 6.2.4.5, “环绕通知(Around Advice)” 中提到的一些注意点。

<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>

doBasicProfiling 通知的实现和@AspectJ中的例子彻底同样(固然要去掉注解):

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}

6.3.3.6. 通知参数

 

Schema-based声明风格和@AspectJ支持同样,支持通知的全名形式 - 经过通知方法参数名字来匹配切入点参数。 参见Section 6.2.4.6, “通知参数(Advice parameters)” 获取详细信息。

若是你但愿显式指定通知方法的参数名(而不是依靠先前说起的侦测策略),能够经过 arg-names 属性来实现。示例以下:

<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>

The arg-names attribute accepts a comma-delimited list of parameter names.

arg-names属性接受由逗号分割的参数名列表。

6.3.3.7. 通知顺序

 

当同一个切入点(执行方法)上有多个通知须要执行时,执行顺序规则在 Section 6.2.4.7, “通知(Advice)顺序” 已经说起了。 切面的优先级经过切面的支持bean是否实现了Ordered接口来决定。

6.3.4. 引入

 

Intrduction (在AspectJ中成为inter-type声明)容许一个切面声明一个通知对象实现指定接口,而且提供了一个接口实现类来表明这些对象。

在 aop:aspect 内部使用 aop:declare-parents 元素定义Introduction。 该元素用于用来声明所匹配的类型有了一个新的父类型(因此有了这个名字)。 例如,给定接口 UsageTracked,以及这个接口的一个实现类 DefaultUsageTracked, 下面声明的切面全部实现service接口的类同时实现 UsageTracked 接口。(好比为了经过JMX暴露statistics。)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+",
implement-interface="UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.SystemArchitecture.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>

The class backing the usageTracking bean would contain the method:

usageTracking bean的支持类能够包含下面的方法:

public void (UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}

欲实现的接口由 implement-interface 属性来指定。 types-matching 属性的值是一个AspectJ类型模式:- 任何匹配类型的bean会实现 UsageTracked 接口。 注意在Before通知的例子中,srevice bean能够用做 UsageTracked 接口的实现。 若是编程形式访问一个bean,你能够这样来写:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

6.3.5. 切面实例化模型

 

Schema-defined切面仅支持一种实例化模型就是singlton模型。其余的实例化模型或许在将来版本中将获得支持。

6.3.6. Advisors

 

"advisors"这个概念来自Spring1.2对AOP的支持,在AspectJ中是没有等价的概念。 advisor就像一个小的自包含的切面,这个切面只有一个通知。 切面自身经过一个bean表示,而且必须实现一个通知接口, 在 Section 7.3.2, “Spring里的通知类型” 中咱们会讨论相应的接口。Advisors能够很好的利用AspectJ切入点表达式。

Spring 2.0 经过 <aop:advisor> 元素来支持advisor 概念。 你将会发现它大多数状况下会和transactional advice一块儿使用,transactional advice在Spring 2.0中有本身的命名空间。格式以下:

<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

和在上面使用的 pointcut-ref 属性同样,你还可使用 pointcut 属性来定义一个内联的切入点表达式。

为了定义一个advisord的优先级以便让通知能够有序,使用 order 属性来定义 advisor的值 Ordered 。

6.3.7. 例子

 

让咱们来看看在 Section 6.2.7, “例子” 提过乐观锁失败重试的例子,若是使用schema对这个例子进行重写是什么效果。

由于乐观锁的关系,有时候business services可能会失败(有人甚至在一开始运行事务的时候就失败了)。 若是从新尝试一下,颇有可能就会成功。对于business services来讲,重试几回是很正常的(Idempotent操做不须要用户参与,不然会得出矛盾的结论) 咱们可能须要透明的重试操做以免让客户看见 OptimisticLockingFailureException 例外被抛出。 很明显,在一个横切多层的状况下,这是很是有必要的,所以经过切面来实现是很理想的。

由于咱们想要重试操做,咱们会须要使用到环绕通知,这样咱们就能够屡次调用proceed()方法。 下面是简单的切面实现(只是一个schema支持的普通Java 类):

public class OptimisticOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
OptimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(OptimisticLockingFailureException ex) {
lockFailureException = ex;
}
}
while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}

请注意切面实现了 Ordered 接口,这样咱们就能够把切面的优先级设定为高于事务通知(咱们每次重试的时候都想要在一个全新的事务中进行)。 maxRetries 和 order 属性均可以在Spring中配置。 主要的动做在 doOptimisticOperation 这个环绕通知中发生。 请注意这个时候咱们全部的 businessService() 方法都会使用这个重试策略。 咱们首先会尝试处理,而后若是咱们获得一个OptimisticLockingFailureException 异常,咱们只须要简单的重试,直到咱们耗尽全部预设的重试次数。

这个类跟咱们在@AspectJ的例子中使用的是相同的,只是没有使用注解。

对应的Spring配置以下:

<aop:config>
<aop:aspect id="optimisticOperationRetry" ref="optimisticOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doOptimisticOperation"/>
</aop:aspect>
</aop:config>
<bean id="optimisticOperationExecutor"
class="com.xyz.myapp.service.impl.OptimisticOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>

请注意咱们如今假设全部的bussiness services都是idempotent。若是不是这样,咱们能够改写切面,加上 Idempotent 注解,让它只调用idempotent:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}

而且对service操做的实现进行注解。这样若是你只但愿改变切面使得idempotent的操做会尝试屡次,你只须要改写切入点表达式,这样只有 @Idempotent 操做会匹配:

  <aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>

6.4. 混合切面类型

 

咱们彻底能够混合使用如下几种风格的切面定义:使用自动代理的@AspectJ 风格的切面,schema-defined <aop:aspect> 的切面,和用 <aop:advisor> 声明的advisor,甚至是使用Spring 1.2风格的代理和拦截器。 因为以上几种风格的切面定义的都使用了相同的底层机制,所以能够很好的共存。

6.5. 代理机制

 

Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象建立代理。(建议尽可能使用JDK的动态代理)

若是被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。全部该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则建立一个CGLIB代理。

若是你但愿强制使用CGLIB代理,(例如:但愿代理目标对象的全部方法,而不仅是实现自接口的方法)那也能够。可是须要考虑如下问题:

  • 没法通知(advise)Final 方法,由于他们不能被覆写。

  • 你须要将CGLIB 2二进制发行包放在classpath下面,与之相较JDK自己就提供了动态代理

强制使用CGLIB代理须要将 <aop:config> 的 proxy-target-class 属性设为true:

<aop:config proxy-target-class="true">
...
</aop:config>

请注意这个属性的设置仅对 每个<aop-config/> 有效; 你可能有多个 <aop-config/>,其中有的强制使用CGLIB代理,有的没有。

当须要使用CGLIB代理和@AspectJ自动代理支持,请按照以下的方式设置 <aop:aspectj-autoproxy> 的 proxy-target-class 属性:

<aop:aspectj-autoproxy proxy-target-class="true"/>

6.6. 编程方式建立@AspectJ代理

 

除了在配置文件中使用 <aop:config> 或者 <aop:aspectj-autoproxy> 来声明切面。 一样能够经过编程方式来建立代理通知(advise)目标对象。关于Spring AOP API的详细介绍,请参看下一章。这里咱们重点介绍自动建立代理。

类 org.springframework.aop.aspectj.annotation.AspectJProxyFactory 能够为@AspectJ切面的目标对象建立一个代理。该类的基本用法很是简单,示例以下。请参看Javadoc获取更详细的信息。

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object
// supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

6.7. 在Spring应用中使用AspectJ

 

到目前为止本章讨论的一直是纯Spring AOP。 在这一节里面咱们将介绍如何使用AspectJ compiler/weaver来代替Spring AOP或者做为它的补充,由于有些时候Spring AOP单独提供的功能也许并不能知足你的须要。

Spring提供了一个小巧的AspectJ aspect library (你能够在程序发行版本中单独使用 spring-aspects.jar 文件,并将其加入到classpath下以使用其中的切面)。 Section 6.7.1, “在Spring中使用AspectJ来为domain object进行依赖注入” 和Section 6.7.2, “Spring中其余的AspectJ切面” 讨论了该库和如何使用该库。 Section 6.7.3, “使用Spring IoC来配置AspectJ的切面” 讨论了如何对经过AspectJ compiler织入的AspectJ切面进行依赖注入。 最后Section 6.7.4, “在Spring应用中使用AspectJ Load-time weaving(LTW)”介绍了使用AspectJ的Spring应用程序如何装载期织入(load-time weaving)。

6.7.1. 在Spring中使用AspectJ来为domain object进行依赖注入

 

Spring容器对application context中定义的bean进行实例化和配置。 一样也能够经过bean factory来为一个已经存在且已经定义为spring bean的对象应用所包含的配置信息。 spring-aspects.jar中包含了一个annotation-driven的切面,提供了能为任何对象进行依赖注入的能力。 这样的支持旨在为 脱离容器管理 建立的对象进行依赖注入。 Domain object常常处于这样的情形:它们多是经过 new 操做符建立的对象, 也多是ORM工具查询数据库的返回结果对象。

包 org.springframework.orm.hibernate.support 中的类 DependencyInjectionInterceptorFactoryBean 可让Spring为Hibernate建立而且配置prototype类型的domain object(使用自动装配或者确切命名的bean原型定义)。 固然,拦截器不支持配置你编程方式建立的对象而非检索数据库返回的对象。 其余framework也会提供相似的技术。还是那句话,Be Pragramatic选择能知足你需求的方法中最简单的那个。 请注意前面说起的类 没有 随Spring发行包一块儿发布。 若是你但愿使用该类,须要从Spring CVS Respository上下载而且自行编译。 你能够在Spring CVS respository下的 'sandbox' 目录下找到该文件。

@Configurable 注解标记了一个类能够经过Spring-driven方式来配置。 在最简单的状况下,咱们只把它看成标记注解:

package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation;
@Configurable
public class Account {
...

当只是简单地做为一个标记接口来使用的时候,Spring将采用和该已注解的类型(好比Account类)全名 (com.xyz.myapp.domain.Account)一致的bean原型定义来配置一个新实例。 因为一个bean默认的名字就是它的全名,因此一个比较方便的办法就是省略定义中的id属性:

<bean class="com.xyz.myapp.domain.Account"
singleton="false">
<property name="fundsTransferService" ref="fundsTransferService"/>
...
</bean>

若是你但愿明确的指定bean原型定义的名字,你能够在注解中直接定义:

package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation;
@Configurable("account")
public class Account {
...

Spring会查找名字为"account"的bean定义,并使用它做为原型定义来配置一个新的Account对象。

你也可使用自动装配来避免手工指定原型定义的名字。 只要设置 @Configurable 注解中的autowire属性就可让Spring来自动装配了: @Configurable(autowire=Autowire.BY_TYPE) 或者 @Configurable(autowire=Autowire.BY_NAME,这样就能够按类型或者按名字自动装配了。

最后,你能够设置 dependencyCheck 属性,经过设置,Spring对新建立和配置的对象的对象引用进行校验 (例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true) )。 若是这个属性被设为true,Spring会在配置结束后校验除了primitives和collections类型的全部的属性是否都被赋值了。

仅仅使用注解并无作任何事情。但当注解存在时,spring-aspects.jar中的 AnnotationBeanConfigurerAspect 就起做用了。 实质上切面作了这些:当初始化一个有 @Configurable 注解的新对象时,Spring按照注解中的属性来配置这个新建立的对象。 要实现上述的操做,已注解的类型必须由AspectJ weaver来织入 - 你可使用一个 build-time ant/maven任务来完成 (参见AspectJ Development Environment Guide) 或者使用load-time weaving(参见 Section 6.7.4, “在Spring应用中使用AspectJ Load-time weaving(LTW)”)。

类 AnnotationBeanConfigurerAspect 自己也须要Spring来配置(得到bean factory的引用,使用bean factory配置新的对象)。 为此Spring AOP命名空间定义了一个很是方便的标签。以下所示,能够很简单的在application context配置文件包含这个标签中。

<aop:spring-configured/>

若是你使用DTD代替Schema,对应的定义以下:

<bean
class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"
factory-method="aspectOf"/>

在切面配置完成 以前 建立的@Configurable对象实例会致使在log中留下一个warning,而且任何对于该对象的配置都不会生效。 举一个例子,一个Spring管理配置的bean在被Spring初始化的时候建立了一个domain object。 对于这样的状况,你须要定义bean属性中的"depends-on"属性来手动指定该bean依赖于configuration切面。

<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
...
</bean>

6.7.1.1.  @Configurable object的单元测试

 

提供 @Configurable 支持的一个目的就是使得domain object的单元测试能够独立进行,不须要经过硬编码查找各类倚赖关系。 若是@Configurable 类型没有经过AspectJ织入, 则在单元测试过程当中注解不会起到任何做用,测试中你能够简单的为对象的mock或者stub属性赋值,而且和正常状况同样的去使用该对象。 若是 @Configurable 类型经过AspectJ织入, 咱们依然能够脱离容器进行单元测试,不过每次建立一个新的 @Configurable 对象时都会看到一个warning标示该对象不受Spring管理配置。

6.7.1.2. 多application context状况下的处理

 

AnnotationBeanConfigurerAspect 经过一个AspectJ singleton切面来实现对 @Configurable 的支持。 一个singleton切面的做用域和一个静态变量的做用域是同样的,例如,对于每个classloader有一个切面来定义类型。 这就意味着若是你在一个classloader层次结构中定义了多个application context的时候就须要考虑在哪里定义 <aop:spring-configured/> bean和在哪一个classpath下放置Srping-aspects.jar。

考虑一下典型的Spring web项目,通常都是由一个父application context定义大部分business service和所须要的其余资源,而后每个servlet拥有一个子application context定义。 全部这些context共存于同一个classloader hierarchy下,所以对于全体context,AnnotationBeanConfigurerAspect 仅能够维护一个引用。 在这样的状况下,咱们推荐在父application context中定义<aop:spring-configured/> bean: 这里所定义的service多是你但愿注入domain object的。 这样作的结果是你不能为子application context中使用@Configurable的domain object配置bean引用(可能你也根本就不但愿那么作!)。

当在一个容器中部署多个web-app的时候,请确保每个web-application使用本身的classloader来加载spring-aspects.jar中的类(例如将spring-aspects.jar放在WEB-INF/lib目录下)。 若是spring-aspects.jar被放在了容器的classpath下(所以也被父classloader加载),则全部的web application将共享一个aspect实例,这可能并非你所想要的。

6.7.2. Spring中其余的AspectJ切面

 

除了 @Configurable 支持,spring-aspects.jar包含了一个AspectJ切面能够用来为那些使用了 @Transactional annotation 的类型和方法驱动Spring事务管理(参见 Chapter 9, 事务管理)。 提供这个的主要目的是有些用户但愿脱离Spring容器使用Spring的事务管理。

对于AspectJ程序员,但愿使用Spring管理配置和事务管理支持,不过他们不想(或者不能)使用注解,spring-aspects.jar也包含了一些抽象切面供你继承来提供你本身的切入点定义。 参见 AbstractBeanConfigurerAspect 和 AbstractTransactionAspect 的Javadoc获取更多信息。 做为一个例子,下面的代码片段展现了如何编写一个切面,而后经过bean原型定义中和类全名匹配的来配置domian object中全部的实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
/**
* The creation of a new bean (any object in the domain model)
*/
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
SystemArchitecture.inDomainModel() &&
this(beanInstance);
}

6.7.3. 使用Spring IoC来配置AspectJ的切面

 

当在Spring application中使用AspectJ的时候,很天然的会想到用Spring来管理这些切面。 AspectJ runtime自身负责切面的建立,这意味着经过Spring来管理AspectJ 建立切面依赖于切面所使用的AspectJ instantiation model(per-clause)。

大多数AspectJ切面都是 singleton 切面。 管理这些切面很是容易,和一般同样建立一个bean定义引用该切面类型就能够了,而且在bean定义中包含 factory-method="aspectOf" 这个属性。 这确保Spring从AspectJ获取切面实例而不是尝试本身去建立该实例。示例以下:

<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf">
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>

对于non-singleton的切面,最简单的配置管理方法是定义一个bean原型定义而且使用@Configurable支持,这样就能够在切面被AspectJ runtime建立后管理它们。

若是你但愿一些@AspectJ切面使用AspectJ来织入(例如使用load-time织入domain object) 和另外一些@AspectJ切面使用Spring AOP,而这些切面都是由Spring来管理的,那你就须要告诉Spring AOP @AspectJ自动代理支持那些切面须要被自动代理。 你能够经过在 <aop:aspectj-autoproxy> 声明中使用一个或多个 include。 每个指定了一种命名格式,只有bean命名至少符合其中一种状况下才会使用Spring AOP自动代理配置:

<aop:aspectj-autoproxy>
<include name="thisBean"/>
<include name="thatBean"/>
</aop:aspectj-autoproxy>

6.7.4. 在Spring应用中使用AspectJ Load-time weaving(LTW)

 

Load-time weaving(LTW)指的是在虚拟机载入字节码文件时动态织入AspectJ切面。 关于LTW的详细信息,请查看 LTW section of the AspectJ Development Environment Guide。 在这里咱们重点来看一下Java 5环境下Spring应用如何配置LTW。

LTW须要定义一个 aop.xml,并将其置于META-INF目录。 AspectJ会自动查找全部可见的classpath下的META-INF/aop.xml文件,而且经过定义内容的合集来配置自身。

一个基本的META-INF/aop.xml文件应该以下所示:

<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN"	"http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="com.xyz.myapp..*"/>
</weaver>
</aspectj>

'include'的内容告诉AspectJ那些类型须要被归入织入过程。使用包名前缀并加上"..*"(表示该子包中的全部类型)是一个不错的默认设定。 使用include元素是很是重要的,否则AspectJ会查找每个应用里面用到的类型(包括Spring的库和其它许多相关库)。一般你并不但愿织入这些类型而且不肯意承担AspectJ尝试去匹配的开销。

但愿在日志中记录LTW的活动,请添加以下选项:

<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN"	"http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver
options="-showWeaveInfo,
-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<include within="com.xyz.myapp..*"/>
</weaver>
</aspectj>

最后,若是但愿精确的控制使用哪些切面,可使用 aspects。 默认状况下全部定义的切面都将被织入(spring-aspects.jar包含了META-INF/aop.xml,定义了配置管理和事务管理切面)。 若是你在使用spring-aspects.jar,可是只但愿使用配制管理切面而不须要事务管理的话,你能够像下面那样定义:

<! DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN"    "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"
>
< aspectj >
< aspects >
< include  within ="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" />
</ aspects >
< weaver
options ="-showWeaveInfo,-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler" >
< include  within ="com.xyz.myapp..*" />
</ weaver >
</ aspectj >

在Java 5平台下,LTW能够经过虚拟机的参数来启用。

-javaagent:<path-to-ajlibs>/aspectjweaver.jar

6.8. 其它资源

 

更多关于AspectJ的信息能够查看 AspectJ home page

Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley, 2005)全面介绍并提供了AspectJ语言参考。

AspectJ in Action by Ramnivas Laddad (Manning, 2003)是一本很是出色介绍AOP的书籍;全书着重介绍了AspectJ,但也对一些通用的AOP场景进行了比较深刻的研究。

相关文章
相关标签/搜索