6Spring AOP

来源:互联网 发布:郑州做网站优化的公司 编辑:程序博客网 时间:2024/06/16 04:53
3Spring AOP
AOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充。AOP专门用于处理系统中分布于各个模块中的交叉关注点的问题。AspectJ是一个java语言的AOP框架,提供了强大的AOP功能。其主要包括两个部分:第一个部分定义了如何表达,定义AOP编程中的语法规范,通过这套语言规范,我们可以方便地用AOP来解决java语言中存在的交叉关注点问题。另一个部分是工具部分,包括编译器,调试工具等。


1 关于面向切面编程的一些术语:
切面(Aspect),一个关注点的模块化,这个关注点可能会横切多个对象,事务管理是J2EE应用中一个关于横切关注点的很好的例子。在SpringAOP中,切面可以使用基于模式或基于@Aspect注解的方式来实现。"切入点"和"处理方法"组成了所谓的"切面"。


连接点(Joinpoint):在程序执行过程中插入切面的地点,比如某个方法调用的时候或处理异常的时候,在Spring AOP中,一个连接点总是表示一个方法的执行。


通知(Advice),在切面的某个特定的连接点上执行的动作,它通知在连接点插入到应用系统中。其中包括"around","before"和"after"等不同类型的通知。许多AOP框架(包括Spring)都是以连接器作通知模型,并维护一个以连接点为中心的拦截器链。


切入点(Pointcut),定义了通知应该应用在那些连接点。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。


引入(Introlduction),用来类添加额外的方法或属性(也被称为连接类型声明(iner-type declaration)。Spring运行引入新的接口(以及一个对应的实现)到任何被代理的对象。


目标对象(Target Object),被一个或多个切面所通知的对象。既然Spring AOP是通过运行是代理实现的,这个对象永远是一个被代理的对象。


AOP代理(AOP Proxy),AOP框架创建的对象,用来实现切面契约(如通知方法执行等等),在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。


织入(Weaving),将切面应用到目标对象从而创建一个新代理对象的过程,这些可以在编译时(如使用Aspecj编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。


通知类型
前置通知(Before advice),在某个连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)


后置通知,在某个连接点正常完成后执行的通知。例如,一个方法没有抛出任何异常,正常返回。


异常通知,在方法抛出异常退出时执行的通知。


最终通知,当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。


环绕通知,包围一个连接点的通知,如方法调用,这个对强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。


环绕通知是最常用通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。


通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。切入点使得通知可以独立对应到面向对象的层次结构中。如一个声明式事务管理的环绕通知可以被应用到一组横跨多个对象的方法上(如service的所有业务操作)。


2 Spring AOP的功能和目标
Spring AOP使用纯Java实现,它不需要专门的编译过程,Spring AOP不需要控制类转载器层次因此它使用于J2EE Web容器或应用服务器。
Spring目前仅支持使用方法调用作为连接点(在Spring bean上通知方法的执行)。虽然可以在不影响到Spring AOP核心API情况下加入对成员变量拦截器支持,但Spring并没有实现成员变量拦截器。如果你需要把对成员变量的访问和更新也作为通知的连接点,可以考虑使用其它框架,如AspectJ。


Spring中提供的内置AOP支持,是基于动态AOP机制实现。从技术角度来讲,所谓动态AOP,即通过动态Proxy模式,在目标对象的方法调用前后插入相应的处理代码。而Spring AOP中的动态Proxy模式,又是基于java Dynamic Proxy(面向interface)实现和CGLib(面向Class)实现的。


Dynamic Proxy与Spring AOP
Dynamic Proxy是JDK1.3版本中新引入的一种动态代理机制。它是Proxy模式的一种动态实现版本。
具体关于java反射和代理的问题可以看21day中的14节。


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


3 @AspectJ支持
Spring 2.0使用和AspectJ 5一样的注解。为了在Spring配置中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保自动代理的Bean是否能被这些切面通知。自动代理是指Spring会判断一个Bean是否使用来一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行。通过你在Spring的配置文件中引入下列元素来启用Spring的@AspectJ的支持:
<aop:aspectj-autoproxy/>
如果你正在使用DTD,通过在你的application context中添加如下定义来启用@AspectJ支持:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
你需要在你的应用程序的classpath中引入两个AspectJ库:aspectjweaver.jar和aspectjrt.jar。可以在Spring-with-dependencies发布包的'lib/aspectj'目录下找到。还需要导入cglib-nodep-2.1_3.jar和asm-2.2.3.jar。


***************************
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用JDK动态代理织入增强,当配置为<aop:aspectj-autoproxy proxy-target-class="true" />时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则Spring将自动使用CGLib动态代理(只要我们导入了cglib-nodep-2.1_3.jar)。


如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换(没有实现接口的就用CGLIB代理,使用了接口的类就用JDK动态代理) 
JDK动态代理和CGLIB字节码生成的区别? 
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。


****************************


4声明一个切面
启用@AspectJ支持后,在application context中定义的任意一个拥有@Aspect注解的bean都将被Spring自动识别并用于配置Spring AOP。


首先在appolication context中定义一个<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注解的类)和其他的类一样有方法和字段的定义。它们也可能包括切入点,通知和引入声明。


在Spring AOP中,拥有切面的类本身不可能是其它切面中通知的目标。一个类上面的@Aspect注解标识它为一个切面,并且从自动代理中排除它。


声明一个切入点
切入点决定了连接点关注的内容,使得我么可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。


一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void类型)。下面的例子定义了一个切入点'anyOldTransfer',这个切入点将匹配任何名为'Transfer'的方法的执行:
@Pointcut ("execution(* transfer(...))")
private void anyOldTransfer(){}
切入点表达式,也就是组成@Pointcut注解的值,是正规的AspectJ 5切入点表达式。


切入点指示符(PCD)的支持
Spring AOP支持在切入点表达式中使用如下AspectJ切入点指示符:
execution - 表示满足某一匹配模式的所有目标类方法连接点。如execution(* greetTo(..))表示所有目标类中的greetTo()方法。这是你将会用到的Spring的最主要的切入点指示符。 


within - 表示特定域下的所有连接点。如within(com.baobaotao.service.*)表示com.baobaotao.service包中的所有 连接点,也即包中所有类的所有方法,而within(com.baobaotao.service.*Service)表示在 com.baobaotao.service包中,所有以Service结尾的类的所有连接点。


this - 代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点


target -  假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。如通过target(com.baobaotao.Waiter)定义的切点,Waiter、以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点。


args - 通过判别目标类方法运行时入参对象的类型定义指定连接点。如args(com.baobaotao.Waiter)表示所有有且仅有一个按类型匹配于Waiter的入参的方法。


@target - 通过判别目标方法的运行时入参对象的类是否标注特定注解来指定连接点。如@args(com.baobaotao.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解。 


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


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


@annotation - 表示标注了特定注解的目标方法连接点。如@annotation(com.baobaotao.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法。


target(),this(),@target()和@within(),和 @annotation()及@args()函数一样,它们也只接受注解类名作为入参。
target()切点函数通过判断目标类是否按类型匹配指定类决定连接点是否匹配,而this()则通过判断代理类是否按类型匹配指定类来决定是否和切点匹配。


Spring AOP还提供了一个名为'bean'的PCD。这个PCD允许你限定匹配连接点到一个特定名称的Spring bean,或者到一个特定名称Spring bean的集合(当使用通配符时)。'bean' PCD具有下列的格式:
bean(idOrNameOfBean)
'idOrNameOfBean'标记可以是任何Spring bean的名字:限定通配符使用'*'来提供,如果你为Spring bean制定一些命名约定,你可以非常容易地编写一个'bean' PCD表达式将它们选出来。和其它连接点指示符一样,'bean' PCD也支持&&, ||和 !逻辑操作符。


Spring AOP是一个基于代理的系统,并且严格区分代理对象本身(对应于'this')和背后的目标对象(对应于'target')


5组合切入点表达式
切入点表达式可以使用&,||,!来组合,还可以通过名字来指向切入点表达式。下面展示了三种切入点表达式:
anyPublicOperation,在一个方法执行连接点代表了任意public方法的执行时匹配。
@Pointcut ("execution(public * *(..))")
private void anyPublicOperation() {}


inTrading,在一个代表了在交易模块(trading)中的任意的方法执行时匹配。
@Pointcut ("within(com.xyz.someapp.trading..*")
private void inTrading() {}


tradingOperation,在一个代表了在交易模块(trading)中的任意的公共方法执行时匹配。
@Pointcut ("anyPublicOperation() && inTrading()")
private void tradingOperation() {}


excution切入点指示符,
excution切入点指示符,执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回类型模式(ret-type-pattern),名字模式(name-pattern)和参数模式(param-pattern)以外,所以的部分都是可选的。
modifiers-pattern是限定模式, throws-pattern是异常模式。
返回类型模是决定了方法的返回类型,使用最频繁的返回类型模式是*,它代表了匹配任意的返回类型。
名字模式匹配的是方法名。 你可以使用*通配符作为所有或者部分命名模式。
参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。
模式(*)匹配了一个接受一个任何类型的参数的方法。模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。
下面是切入点指示符的例子:
任意公共方法的执行:
execution(public * *(..))


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


IAccountService 接口的任意方法的执行:
execution(* com.xyz.service.IAccountService.*(..))


定义在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)


实现AccountService接口的目标对象的任意连接点(在Spring AOP中只是方法执行):
target (com.xyz.service.AccountService)


任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
args (java.io.Serializable)
不同于execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。


目标对象中有一个 @Transactional 注解的任意连接点(在Spring AOP中只是方法执行)
@target (org.springframework.transaction.annotation.Transactional)


任何一个目标对象声明的类型有一个 @Transactional 注解的连接点
@within (org.springframework.transaction.annotation.Transactional)


任何一个执行的方法有一个 @Transactional 注解的连接点
@annotation (org.springframework.transaction.annotation.Transactional)


任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点
@args (com.xyz.security.Classified)


任何一个在名为'tradeService'的Spring bean之上的连接点
bean (tradeService)


任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点
bean (*Service)


6声明通知
通知是跟一个切入点表达式关联的,并且在切入点匹配的方法执行之前或之后或前后运行。声明通知的括号中放入的是切入点表达式或者某个切面下的切入点。
前置通知
一个切面里使用@Before注解声明前置通知。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;


@Aspect
public class BeforeExample {
  @PointCut("execution(* com.xyz.myapp.dao.*.*(..))")
  public void dataAccessOperation(){}


  @Before ("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
    // ...
  }
}
如果使用了切入点表达式,则可以把上面的例子换个写法:
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() {
    // ...
  }
}


后置通知
后置通知通常在一个匹配的方法返回的时候执行。使用@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属性中使用的名字(retVal)必须对应于通知方法内的一个参数名。当一个方法执行返回后,返回值作为响应的参数值传入通知方法。一个returning子句也限制了只能匹配到返回制定类型值的方法。本例中retVal是Object类型,也就是说返回任意类型都会匹配。


异常通知
抛出异常通知在一个方法抛出异常后执行。使用@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异常。


最终通知
不论一个方法是如何结束,最终通知都会运行。使用@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() {
    // ...
  }
}


环绕通知
最后一种通知是环绕通知。环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个连接点方法执行之前和执行之后运行。而且它可以决定这个连接点方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。


环绕通知使用@Around注解来声明。通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内,调用ProceedingJoinPoint的proceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。
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可能在通知体内部被调用一次,许多次,或者根本不被调用,所有这些都是合法的。


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


传递参数给通知
为了可以在通知体内访问参数, 你可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方 使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。用一个例子应该会使它变得清晰。如:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account){
  // ...
}
切入点表达式的 args(account,..) 部分有两个目的:首先它保证了 只会匹配那些接受至少一个参数的方法的执行,而且传入的参数必须是Account类型的实例, 其次它使得在通知体内可以通过account 参数访问实际的Account对象。


另外一个办法是定义一个切入点,这个切入点在匹配某个连接点的时候“提供”了 Account对象的值,然后直接从通知中访问那个命名切入点。看起来和下面的示例一样:
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
  // ...
}




7基于Schema的AOP支持
在Spring的配置文件中,所有的切面和通知都必须定义在<aop:config>元素内部。 (一个application context可以包含多个 <aop:config>)。 一个<aop:config>可以包含pointcut,advisor和aspect元素(注意这三个元素必须按照这个顺序进行声明)。 


声明切面
有了schema的支持,切面就和常规的Java对象一样被定义成application context中的一个bean。
切面使用<aop:aspect>来声明,通过 ref 属性来引用:
<aop:config>
  <aop:aspect id="myAspect" ref="aBean">
    ...
  </aop:aspect>
</aop:config>


<bean id="aBean" class="...">
  ...
</bean>
切面的支持bean(上例中的"aBean")可以象其他Spring bean一样被容器管理配置以及依赖注入。<aop:aspect>中id与ref和<bean>元素的一样。


声明切入点
一个命名切入点可以在<aop:config>元素中定义,这样多个切面和通知就可以共享该切入点。一个描述service层中所有service执行的切入点可以定义如下:
<aop:config>
  <aop:pointcut id="businessService" 
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
也可以在一个切面里声明一个切入点:
<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'可以分别用来代替'&&', '||' 和 '!'。如:
<aop:config>
  <aop:aspect id="myAspect" ref="aBean">
    <aop:pointcut id="businessService" 
          expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
    <aop:before pointcut-ref="businessService" method="monitor"/>
    ...
    </aop:aspect>
</aop:config>


public void monitor(Object service){
    ...
}


前置通知
前置通知在匹配方法执行前运行。在<aop:aspect>中使用<aop:before> 元素来声明它。
<aop:aspect id="beforeExample" ref="aBean">
<aop:pointcut id="dataAccessOperation" expression="execution(* lifecenter.manage.iface.service.*Service.*(..))"/>
    <aop:before 
      pointcut-ref="dataAccessOperation" 
      method="doAccessCheck"/>     
    ...
</aop:aspect>
Method属性标识了提供通知主体的方法(切面类中doAccessCheck方法)。 这个方法必须定义在包含通知的切面元素所引用的bean中。
这里的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>


后置通知
后置通知在匹配的方法完全执行后运行。和前置通知一样,可以在<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){...}


异常通知
异常通知在匹配方法抛出异常退出时执行。在<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:pointcut id="dataAccessOperation" expression="execution(* lifecenter.manage.iface.service.*Service.*(..))"/>
    <aop:after-throwing 
      pointcut-ref="dataAccessOperation"
      throwing="dataAccessEx" 
      method="doRecoveryActions"/>   
    ...
</aop:aspect>
doRecoveryActions方法必须声明一个名字为dataAccessEx的参数。 参数的类型依照@AfterThrowing所描述的方法强制匹配。例如:方法签名可以如下这般声明:
@Component
public void doRecoveryActions(DataAccessException dataAccessEx) {
System.out.println("advice************");
dataAccessEx.printStackTrace();//打印发生异常的切入点的类的方法的异常栈
//下面对发生异常的切入点的类的方法抛出自定义的异常类LifecenterException;这样对发生异常的方法的异常捕获就变成捕获LifecenterException
throw new LifecenterException();
}


最终通知
最终通知无论如何都会在匹配方法退出后执行。使用after元素来声明它:
<aop:aspect id="afterFinallyExample" ref="aBean">
    <aop:after
      pointcut-ref="dataAccessOperation" 
      method="doReleaseLock"/>     
    ... 
</aop:aspect>


环绕通知
Around通知使用aop:around元素来声明。通知方法的第一个参数的类型必须是 ProceedingJoinPoint类型。在通知的主体中,调用 ProceedingJoinPoint的proceed()方法来执行真正的方法。 proceed方法也可能会被调用并且传入一个Object[]对象 - 该数组将作为方法执行时候的参数。
<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;
}


Advisor(通知器)
advisor:有两个属性分别是advice和pointcut,通过这两个属性,可以分别配置Advice和Pointcut。通过Advisor,可以定义应该使用哪个Advice并在哪个Pointcut使用它,也就是说通过Advisor把Advice和Pointcut结合起来了。Advisor中既有切入点又有通知,那么其实它也就类似一个切面。
祖先接口为org.springframework.aop.Advisor,应用中可直接使用org.springframework.aop.support.DefaultPointcutAdvisor


Advice:用于定义拦截行为,祖先接口为org.aopalliance.aop.Advice,该接口只是标识接口,应用中可直接实现BeforeAdvice ,ThrowsAdvice,MethodInterceptor ,AfterReturningAdvice ,IntroductionInterceptor 等子接口


Pointcut:用于定义拦截目标集合,祖先接口为org.springframework.aop.Pointcut


Spring中的Advisor,Advice,Point的应用:
编写Advisor实现类
在此可直接使用org.springframework.aop.support.DefaultPointcutAdvisor


编写Advice实现类
public class PlayAdvice implements MethodBeforeAdvice{
 public void before(Method method, Object[] args, Object target)
   throws Throwable {
  System.out.println("my before advice");
 // method.invoke(target, args); 如果再调用这句,则目标方法会执行多一次
 }
}






Spring 2.0通过<aop:advisor>元素来支持advisor概念。 你将会发现大多数情况下它会和transactional advice(事务通知)一起使用,在Spring 2.0中它有自己的命名空间。其格式如下:
<aop:config proxy-target-class="false">
<aop:advisor pointcut="execution(* com.bizyi.dgzz.manage.iface.service.*Service.*(..))"
advice-ref="txAdvice" />


</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" read-only="true" propagation="SUPPORTS" />
<tx:method name="add*" propagation="REQUIRED"
no-rollback-for="RuntimeException" rollback-for="ServiceException,NafServiceException" />
<tx:method name="del*" propagation="REQUIRED"
no-rollback-for="RuntimeException" rollback-for="ServiceException,NafServiceException" />
<tx:method name="upd*" propagation="REQUIRED"
no-rollback-for="RuntimeException" rollback-for="ServiceException,NafServiceException" />
</tx:attributes>
</tx:advice>
和上面所使用的pointcut-ref属性一样,你还可以使用pointcut 属性来定义一个内联的切入点表达式。
为了定义一个advisor的优先级以便让通知具有次序,使用order属性来定义advisor中 Ordered的值。也可以对<aop:aspect>元素配置切面的优先级则<aop:aspect order="1">...</aop:aspect>元素中有order属性来指定切面的优先级。order值越小,优先级越高。


8Spring AOP中使用@AspectJ还是XML?
首先,对注解的支持是在Java5版本以后,所以,如果你使用的是java5版本以下的JVM, 
不用考虑,必须选择XML风格 (XML配置形式的),而非注解形式(AspectJ风格)的。 


使用XML风格,则所有的切面、切点、通知等配置都写在一个或几个Spring配置文件里。 
这样的好处是,从配置文件中,就可以很清晰的看出系统中的有哪些切面,某个切面里使用那个的 
通知(advice)以及通知(advice)作用的切点。而在AspectJ风格中,在java程序中标识切面则显得凌乱、模糊。 


XML风格的AOP仅仅支持"singleton"切面实例模型,而采用AspectJ风格的AOP则没有这个限制。 


XML风格的AOP中是不支持命名连接点的声明,而采用AspectJ风格的AOP则没有这个限制。


9AOP的实例:
需要的包:aspectjweaver.jar和aspectjrt.jar,cglib-nodep-2.1_3.jar,asm-2.2.3.jar。
基于@AspectJ的支持
首先我们在中启用Spring的@AspectJ的支持,需要在Spring的配置文件applicationContext.xml中添加:<aop:aspectj-autoproxy/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx          
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byType">
<aop:aspectj-autoproxy/>
.....
</beans>


然后我们在applicationContext.xml中配置切面类PreGreetingAspect.java这个Bean,当然如果我们使用Spring注解的话就不需要这样配置,直接使用@Component注解这个切面类就可以了。
<bean id="preGreetingAspect" class="action.aop.PreGreetingAspect" />


最后我们来编写切面类PreGreetingAspect.java:
package action.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class PreGreetingAspect
{
//前置通知,在执行action.area.getArea.java类中以get开头的方法前执行beforeGreeting()方法。
    @Before("execution(* action.area.getArea.get*(..))")
    public void beforeGreeting(){
        System.out.println("*****hello Spring AOP*********");
    }
}
这里的前置通知中放入的是切入点表达式。我们也可以放入切入点如:
package action.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


@Aspect
@Component
@Order(1)
public class PreGreetingAspect
{
    //声明一个切入点
    @Pointcut ("execution(* action.area.getArea.get*(..))")
    public void aop1(){}
    
   //前置通知,使用aop1()切入点
    @Before("aop1()")
    public void beforeGreeting(){
        System.out.println("*****hello Spring AOP*********");
    }


    //后置通知,使用切入点aop1
    @After("action.aop.PreGreetingAspect.aop1()")
    public void doAfter(){
        System.out.println("********do after1********");
    }
}
我们在切面类PreGreetingAspect中声明了一个切入点aop1(),那么我们也可以在其他切面类的通知用使用这个切入点如:
package action.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;


@Aspect
@Component
@Order(2)
public class PreGreetingAspect2
{
    //后置通知,使用切面类PreGreetingAspect下的切入点aop1
    @After("action.aop.PreGreetingAspect.aop1()")
    public void doAfter(){
        System.out.println("********do after2********");
    }
}


如果有多个通知想要在同一连接点运行会发生什么?Spring AOP遵循跟AspectJ一样的优先规则来确定通知执行的顺序。 在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。
在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。


当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定切面的优先级,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。 在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用Order注解做到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。如果想在xml配置切面的优先级则<aop:aspect order="1">...</aop:aspect>元素中有order属性来指定切面的优先级。


当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的。
所以上面的执行结果为:
*****hello Spring AOP*********
********do after2********
********do after1********


基于Schema的AOP支持
首先Spring配置文件中添加切入点的配置,切面的配置,通知的配置。如:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx          
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byType">
<!-- start AOP -->
<aop:config>
<!-- 切入点配置 -->
 <aop:pointcut id="testAOP" expression="execution(* action.area.getArea.get*(..))"/>
<!-- 切面配置 -->
  <aop:aspect id="myAspect" ref="preGreetingAspect">
<!-- 通知配置 -->
  <aop:before pointcut-ref="testAOP" method="beforeGreeting"/>  
  </aop:aspect>
</aop:config>


<!--
由于切面类PreGreetingAspect.java采用@Component注解,所以我们不需要在这里再配置对应的<bean>元素
<bean id="preGreetingAspect" class="action.aop.PreGreetingAspect">
-->
....
<beans>


最后我们来编写切面类PreGreetingAspect.java:
package action.aop;
import org.springframework.stereotype.Component;


@Component
public class PreGreetingAspect
{
    public void beforeGreeting(){
        System.out.println("*****hello Spring AOP*********");
    }
}

0 0
原创粉丝点击