学习AOP有段时间了,一直没空总结一下,致使有些知识点都遗忘了,以后会把之前学过的Spring核心相关的知识点总结一轮...java
先大致介绍下Spring AOP的特色(均摘自"Spring in action第四版"):正则表达式
Spring支持了AOP,另外还有不少实现了AOP的技术,例如AspectJ,它补充了Spring AOP框架的功能,他们之间有着大量的协做,并且Spring AOP中大量借鉴了AspectJ项目,Spring AOP相对粗粒度,而AspectJ提供更强大更细粒度的控制,以及更丰富的AOP工具集,但须要额外的语法学习;spring
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP,编程模型几乎与编写成熟的AspectJ注解切面彻底一致。这种AOP风格的好处在于可以不使用XML来完成功能。Spring AOP构建在动态代理之上,所以,Spring对AOP的支持局限于方法拦截;若是你对AOP的需求超过了建党方法调用(如构造器或属性拦截),那么你须要AspectJ来实现切面;编程
Spring提供了4种类型的AOP支持:app
基于代理的经典Spring AOP;框架
纯POJO切面;工具
@AspectJ注解驱动的切面;学习
注入式AspectJ切面(适用于Spring各版本);spa
AOP相关的术语(概念):3d
1.通知(Advice):
切面的工做被称为通知,通知定义了切面是什么以及什么时候使用;
分5类:
前置通知(Before) | 在目标方法被调用以前调用通知功能; |
后置通知(After) | 在目标方法完成以后调用通知,此时不会关心方法的输出是什么; |
返回通知(After-returning) | 在目标方法成功执行以后调用通知; |
异常通知(After-throwing) | 在目标方法抛出异常后调用通知; |
环绕通知(Around) | 通知包裹了被通知的方法,在被通知的方法调用以前和调用以后 |
2.链接点(Join point)
应用通知的时机被称为链接点;链接点是在应用执行过程当中可以插入切面的一个点。这个点能够是调用方法时、抛出异常时、甚至修改一个字段时。切面代码能够利用这些点插入到应用的正常流程之中,并添加新的行为;
3.切点(Poincut)
切点定义了切面的在何处实施;一般使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名来指定;
4.切面Aspect
是通知(什么时候,作什么)和切点(何处)的结合;
5.引入(Introduction)
容许咱们向现有的类添加新方法或新属性;
6.织入(Weaving)
把切面应用到目标对象并建立新的代理对象的过程
3种织入时机:
编译期 | 在目标类编译时被织入,AspectJ的织入编译器就是以这种方式织入的 |
类加载期 | 在目标类加载到JVM中时被织入,AspectJ5的加载时织入(load-time weaving,LTW)支持 |
运行期 | 在运行的某个时刻被织入,通常状况下AOP容器会为目标对象动态建立一个代理对象,Spring AOP就支持这种方式 |
Spring AOP是基于动态代理的:
经过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。以下图所
示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当
代理拦截到方法调用时,在调用目标bean方法以前,会执行切面逻辑。
编写切点(若干图例了解切点表达式):
咱们使用execution()指示器选择Performance的perform()方法。方法表达式以“*”号开始,代表了咱们不关心方法返回值的类型。而后,咱们指定了全限定类名和方法名。对于方法参数列表,咱们使用两个点号(..)代表切点要选择任意的perform()方法,不管该方法的入参是什么。
咱们使用了“&&”操做符把execution()和within()指示器链接在一块儿造成与(and)关系(切点必须匹配全部的指示器)。相似地,咱们可使用“||”操做符来标识或(or)关系,而使用“!”操做符来标识非(not)操做。由于“&”在XML中有特殊含义,因此在Spring的XML配置里面描述切点时,咱们可使用and来代替“&&”。一样,or和not能够分别用来代替“||”和“!”。
在这里,咱们但愿在执行Performance的perform()方法时应用通知,但限定bean的ID为woodstock。
下面正式开始建立切面:
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { @Before("execution(** concert.Performance.perform(..))") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("execution(** concert.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("execution(** concert.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP CLAP..."); } @AfterThrowing("execution(** concert.Performance.perform(..))") public void demandRefund() { System.out.println("Demanding a refund"); } }
Audience类使用@AspectJ注解进行了标注。该注解代表Audience不只仅是一个POJO,仍是一个切面。Audience类中的方法都使用注解来定义切面的具体行为。Audience有四个方法,定义了一个观众在观看演出时可能会作的事情。在演出以前,观众要就坐(takeSeats())并将手机调至静音状态(silenceCellPhones();
AspectJ提供了五个注解来定义通知
这样,一个切面就定义好了,但咱们四个注解使用的表达式都同样,能够用@Pointcut注解提供表达式的引用:
1.提供一个空方法,在上面增长注解:@Pointcut("execution(** concert.Performance.perform(..))");
2.其它注解引用,例如: @Before("上面定义的空方法名()");
光这样还不够,下面要装配Audience类成为一个bean:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy //气筒AspectJ自动代理 @ComponentScan public class ConcertConfig { //bean的配置类 @Bean public Audience audience() { //配置Audience类成为一个Bean return new Audience(); } }
以上就实现了前置后置分离通知的切面;
接下在介绍另外一种:环绕通知的写法(逻辑不复杂的时候建议用这种方式,更加直观,一旦逻辑复杂,可读性会不好):
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Around; import org.aspectj.lang.Aspect; import org.aspectj.lang.Pointcut; @Aspect public void Audience { @Pointcut("execution(** concert.Performance.perform(..))") public void performance(){} @Around("performance()") public void watchPerformance(PerceedingJoinPoint jp){ try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); //调用被通知的方法(能够屡次调用) System.out.println("CLAP CLAP CLAP..."); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
以上总结的是不带参数构造切面的状况
接下来介绍若是在通知上符加参数的状况:
import java.util.HashMap; import java.util.Map; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class TrackCounter { private Map<Integer, Interger> trackCounts = new HashMap<Integer, Integer>(); @Pointcut( "execution(* soundsystem.CompactDisc.playTrack(int))" + "&& args(trackNumber)") public void trackPlayed(int trackNumber) {} @Before("trackPlayed(trackNumber)") public void countTrack(int trackNumber){ int currentCount = getPlayCOunt(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } public int getPlayCount(int trackNumber) { return trackCOunts.containKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
图例来解释表达式: