`

spring的切面的讲解

阅读更多

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

面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)以外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。 (这些关注点术语通常称作 横切(crosscutting) 关注点。)
Spring的一个关键的组件就是 AOP框架。 尽管如此,Spring IoC容器并不依赖于AOP,这意味着你可以自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善。
Spring 2.0 AOP
Spring 2.0 引入了一种更加简单并且更强大的方式来自定义切面,用户可以选择使用基于模式(schema-based)的方式或者使用@AspectJ注解。 这两种风格都完全支持通知(Advice)类型和AspectJ的切入点语言,虽然实际上仍然使用Spring AOP进行织入(Weaving)。
本章主要讨论Spring 2.0对基于模式和基于@AspectJ的AOP支持。请查阅"AOP声明风格的选择"一节获取 为你的应用选择适当的声明风格的建议。Spring 2.0完全保留了对Spring 1.2的向下兼容性,下一章 将讨论 Spring 1.2 API所提供的底层的AOP支持。
Spring中所使用的AOP:
提供声明式企业服务,特别是为了替代EJB声明式服务。 最重要的服务是 声明性事务管理(declarative transaction management) , 这个服务建立在Spring的抽象事务管理(transaction abstraction)之上。
允许用户实现自定义的切面,用AOP来完善OOP的使用。
这样你可以把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特有的。 不幸的是,AOP术语并不是特别的直观;如果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支持
"@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.8, “在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.AnnotationAwareAspectJAut­oProxyCreator" />
你需要在你的应用程序的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 注解风格的 AOP 中,一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用 @Pointcut 注解来表示(作为切入点签名的方法必须返回 void 类型)。
用一个例子会帮助我们区分切入点签名和切入点表达式之间的差别,下面的例子定义了一个切入点'anyOldTransfer', 这个切入点将匹配任何名为 "transfer" 的方法的执行:
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
切入点表达式,也就是 @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切入点指定者:
其他的切入点类型
完整的AspectJ切入点语言支持额外的切入点指定者,但是Spring不支持这个功能。 他们分别是call, initialization, preinitialization, staticinitialization, get, set, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this 和 @withincode。 在Spring AOP中使用这些指定者将会导致抛出IllegalArgumentException异常。
Spring AOP支持的切入点指定者可能在将来的版本中得到扩展,不但支持更多的AspectJ 切入点指定者(例如"if"),还会支持某些Spring特有的切入点指定者,比如"bean"(用于匹配bean的名字)。
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成员可视性访问规则。 (比如说,你可以在同一类型中访问私有的切入点,在继承关系中访问受保护的切入点,可以在任意地方访问公共切入点。 成员可视性访问规则不影响到切入点的 匹配。
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?)
除了返回类型模式(上面代码片断中的ret-type-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 注解声明前置通知:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}

如果使用一个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()(打印出正在被通知的方法的有用信息)。详细的内容请参考Javadocs。
6.2.4.6.2. 传递参数给通知(Advice)
我们已经看到了如何绑定返回值或者异常(使用后置通知(after returning)和异常后通知(after throwing advice)。 为了可以在通知(adivce)体内访问参数,你可以使用 args 来绑定。 如果在一个参数表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。 可能给出一个例子会更好理解。假使你想要通知(advise)接受某个Account对象作为第一个参数的DAO操作的执行,你想要在通知体内也能访问到acc­ount对象,你可以写如下的代码:
@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使用如下的策略来决定参数名字:
如果参数名字已经被用户明确指定,则使用指定的参数名: 通知(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 参数了,因为编译器会自动完成这一工作。
使用 'argNames' 属性有点不那么优雅,所以如果没有指定'argNames' 属性, Spring AOP 会寻找类的debug信息,并且尝试从本地变量表(local variable table)中来决定参数名字。 只要编译的时候使用了debug信息(至少要使用 '-g:vars' ),就可获得这些信息。 使用这个flag编译的结果是: (1)你的代码将能够更加容易的读懂(反向工程), (2)生成的class文件会稍许大一些(通常是不重要的), (3)移除不被使用的本地变量的优化功能将会失效。 换句话说,你在使用这个flag的时候不会遇到任何困难。
如果不加上debug信息来编译的话,Spring AOP将会尝试推断参数的绑定。 (例如,要是只有一个变量被绑定到切入点表达式(pointcut expression)、通知方法(advice method)将会接受这个参数, 这是显而易见的)。 如果变量的绑定不明确,将会抛出一个 AmbiguousBindingException 异常。
如果以上所有策略都失败了,将会抛出一个 IllegalArgumentException 异常。
分享到:
评论

相关推荐

    spring 用到的三个切面的jar包

    学习spring4 ,关于aop 切面的讲解,用到的三个jar包。aopalliance.jar aspectjweaver-1.6.6.jar spring-aspects-4.0.6.RELEASE.jar

    精通Spring AOP切面编程

    深入讲解Spring应用中重要的一部分AOP--面向切面编程

    SpringAOP切面实例讲解及应用场景(通俗易懂)

    下面是一个在spring mvc中关于切面如何使用的例子,可以指直观的理解切面到底有什么作用 1、引用 AspectJ jar 包依赖 pom.xml 文件添加依赖 org.aspectj aspectjrt 1.9.2 2、创建两个新的包 ...

    Spring Security、Spring Social 、Spring Security OAuth

    它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制...

    Spring重要概念 AOP DI 讲解

    Spring重要概念 AOP DI 讲解,20来页课件,讲解最关键的两个特性 控制反转/依赖注入 和 面向切面编程.

    java面试Spring.pdf

    AOP(Aspect-OrientedProgramming),面向切面编程 Spring AOP里面常用名词的概念: Spring容器的启动流程 Spring Bean的生命周期? Spring中bean的作用域 说一下Spring基于xml注入bean的几种方式? Spring如何解决循环...

    Spring框架文档 - 核心技术部分 中文版 - 第一部分.pdf

    在对Spring框架的IoC容器进行全面介绍之后,将全面介绍Spring的面向切面编程(AOP)技术。Spring框架(Spring Framework)有自己的AOP框架,在概念上很容易理解,它成功地解决了Java企业编程中80%的AOP需求。 本文档...

    Spring框架文档 - 核心技术部分 中英双语版 第一部分.pdf

    在对Spring框架的IoC容器进行全面介绍之后,将全面介绍Spring的面向切面编程(AOP)技术。Spring框架(Spring Framework)有自己的AOP框架,在概念上很容易理解,它成功地解决了Java企业编程中80%的AOP需求。 本文档...

    Spring框架文档 - 核心技术部分 中英双语版 第二部分.pdf

    在对Spring框架的IoC容器进行全面介绍之后,将全面介绍Spring的面向切面编程(AOP)技术。Spring框架(Spring Framework)有自己的AOP框架,在概念上很容易理解,它成功地解决了Java企业编程中80%的AOP需求。 本文档...

    Spring框架文档 - 核心技术部分 中文版 - 第二部分.pdf

    在对Spring框架的IoC容器进行全面介绍之后,将全面介绍Spring的面向切面编程(AOP)技术。Spring框架(Spring Framework)有自己的AOP框架,在概念上很容易理解,它成功地解决了Java企业编程中80%的AOP需求。 本文档...

    精通spring--源代码

    包括控制反转容器,面向切面编程  对DA0层集成技术进行了详细阐述。包括DA0抽象支持。JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成  深入讲解Java EE服务及技术集成。包括JNDI集成,EJB...

    精通Spring(书签)

    包括控制反转容器,面向切面编程  对DA0层集成技术进行了详细阐述。包括DA0抽象支持。JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成  深入讲解Java EE服务及技术集成。包括JNDI集成,EJB...

    SSH(Struts1.0+Spring+Hibernate)框架集成笔记

    仅凭看视频是远远不够的,因为这些涉及了尤其是spring底层的好多类以及控制翻转(IOC)和面向切面(AOP)编程的思想,不过在讲述过程中我们尽量以实例来解释spring中的这些思想,不做空的理论上的讲解,在实践中理解...

    Spring(aop)讲解

    (aspect oriented programming)面向切面(方面)编程。 是对所有对象或者是一类对象编程,核心是(在不增加代码的基础上,还增加新功能)

    Spring学习总结(不含整合其他框架)

    文档对Spring的知识做了一个全面的总结,具体讲解Spring的控制反转、依赖注入、面向切面编程、Spring中的Bean配置、Spring对JDBC的支持以及Spring的声明式事务

    spring课堂笔录

    全面的讲解了spring所有的知识!可以跟好的帮助你学习spring,里面有ioc aop di等一些切面编程思想!

    精通Spring (书签版)

    包括控制反转容器,面向切面编程  对DA0层集成技术进行了详细阐述。包括DA0抽象支持。JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成  深入讲解Java EE服务及技术集成。包括JNDI集成,EJB...

    精通Spring(书签版)

    包括控制反转容器,面向切面编程  对DA0层集成技术进行了详细阐述。包括DA0抽象支持。JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成  深入讲解Java EE服务及技术集成。包括JNDI集成,EJB...

    Spring框架进阶:成为Java技术大牛的秘密.zip

    文章涵盖了Spring框架的模块化设计、控制反转(IoC)、依赖注入(DI)、面向切面编程(AOP)、Spring MVC和Web开发、Spring Data和数据访问等内容。此外,文中还提供了实践项目开发的建议,帮助读者将所学知识运用到...

    spring中AOP个人总结

    aop(aspect oriented programming)面向切面编程是spring当中一个重要内容,在学习之后感觉这个思想挺不错的,做个总结 AOP讲解 一、面向切面编程 ​ 听说过面向对象编程(oop),但是面向切面编程还真是第一次...

Global site tag (gtag.js) - Google Analytics