分享

Spring之AOP

 爱学习_守之 2015-03-14
参见:http://blog.csdn.net/chjttony/article/details/6303141

AOP基本概念:Aspect-Oriented Programming,面向方面编程的简称,Aspect是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern),从关注点中分离出横切关注点是面向方面程序设计的核心所在。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过方面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。

AOP与OOP的区别:面向方面编程AOP和面向对象编程OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

AOP常用的技术以及实现:
常用的AOP技术有:
1、AspectJ:源代码和字节码级别的方面编织器,用户需要使用不同于Java的新语言。
2、AspectWerkz:AOP框架,使用字节码动态编织器和XML配置。
3、JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上。
AOP中使用的一些实现技术有:
1、BCEL:Byte-Code Engineering Library,Java字节码操作类库。
2、Javassist:Java字节码操作类库,JBoss的一个子项目。

面向方面编程(AOP)常用术语:
1、切面Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些切入点Pointcut 以及对切入点进行相应的操作的通知Advice。
2、连接点Join Point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它连接点jointpoint。
3、切入点Pointcut:表示一组连接点jointpoint,这些连接点或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的操作处理通知Advice将要发生的地方。
4、通知Advice:Advice 定义了在切入点pointcut 里面定义的程序点具体要做的操作和处理,它通过 before、after 和 around 来区别是在每个切入点之前、之后还是代替执行的代码。
5、目标对象Target:代理的目标对象,即切面要被应用到的目标对象。
6、织入Weave:指将切面应用到目标对象,并导致代理对象创建的过程。

AOP的体系结构:
AOP联盟定义的AOP体系结构如下:
层次3语言和开发环境:基础是指待增加对象或者目标对象;切面通常包括对于基础的增加应用;配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现。 
层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象、切面和配置使用AOP的API转换、抽象、封装成面向方面中的逻辑模型。 
层次1底层编织实现模块:主要是将面向方面系统抽象封装的AOP模型编织进待增强的目标对象的实现技术。

通知Advice:Advice通知是AOP联盟定义的一个接口,定义当拦截到连接点做相应的处理操作,为切面增强提供织入接口。在Spring AOP中,通知主要描述Spring AOP围绕方法调用而注入切面的行为,Spring AOP的通知扩展了AOP联盟的通知接口,提供了前置通知BeforeAdvice、后置通知AfterReturnAdvice、最终通知AfterAdvice和例外通知ThrowAdvice等。
1、以一个方法为例,讲解Spring中通知的类型:
  1. public 返回类型 方法名(参数列表){ ——>环绕通知    
  2.        方法前处理代码    ——>     前置通知    
  3.    try{    
  4.        方法具体实现(方法体)…….    
  5.        方法后处理代码    ——>     后置通知    
  6.    }Catch(异常类型 e){    
  7.        异常处理……       ——>     例外通知    
  8.    }finally{    
  9.       最后处理代理……       ——>     最终通知    
  10.    }    

2、前置通知BeforeAdvice:
前置通知源码如下:
  1. package org.springframework.aop;  
  2. import org.aopalliance.aop.Advice;  
  3. public interface BeforeAdvice extends Advice {  
  4. }  
其中Advice是AOP联盟提供的通知接口。
前置通知子接口方法前置通知MethodBeforeAdvice:
在前置通知BeforeAdvice的继承关系中,定义了为代增强目标方法设置的前置增强接口方法前置通知MethodBeforeAdvice,源码如下:
  1. public interface MethodBeforeAdvice extends BeforeAdvice {  
  2.     //前置方法通知的回调方法,在目标对象方法被调用之前调用  
  3.     //method参数是目标方法的反射对象,args是目标方法的输入参数数组  
  4.     void before(Method method, Object[] args, Object target) throws Throwable;  

前置方法通知实现类CountingBeforeAdvice:方法前置通知MethodBeforeAdvice接口的实现类比较多,以最简单的CountingBeforeAdvice为例,分析方法前置通知所做的具体操作,CountingBeforeAdvice的源码如下:
  1. public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {  
  2.     //实现方法前置通知MethodBeforeAdvice接口的方法  
  3.     public void before(Method m, Object[] args, Object target) throws Throwable {  
  4.     //以目标对象方法作为参数,调用父类MethodCounter的count方法统计方法调用次数  
  5.         count(m);  
  6.     }  
  7. }  
  8. CountingBeforeAdvice的父类MethodCounter的源码如下:  
  9. public class MethodCounter implements Serializable {  
  10.     //方法名—>方法调用次数的map集合,存储方法的调用次数  
  11.     private HashMap<String, Integer> map = new HashMap<String, Integer>();  
  12.     //所有方法的总调用次数  
  13.     private int allCount;  
  14.     //统计方法调用次数,CountingBeforeAdvice的调用入口  
  15.     protected void count(Method m) {  
  16.         count(m.getName());  
  17.     }  
  18.     //统计指定名称方法的调用次数  
  19.     protected void count(String methodName) {  
  20.         //从方法名—>方法调用次数集合中获取指定名称方法的调用次数  
  21.         Integer i = map.get(methodName);  
  22. //如果调用次数不为null,则将调用次数加1,如果调用次数为null,则设置调用次数为1  
  23.         i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);  
  24.         //重新设置集合中保存的方法调用次数  
  25.         map.put(methodName, i);  
  26.         //所有方法总调用次数加1  
  27.         ++allCount;  
  28.     }  
  29.     //获取指定名称方法的调用次数  
  30.     public int getCalls(String methodName) {  
  31.         Integer i = map.get(methodName);  
  32.         return (i != null ? i.intValue() : 0);  
  33.     }  
  34.     //获取所有方法的总调用次数  
  35.     public int getCalls() {  
  36.         return allCount;  
  37.     }  
  38.     public boolean equals(Object other) {  
  39.         return (other != null && other.getClass() == this.getClass());  
  40.     }  
  41.     public int hashCode() {  
  42.         return getClass().hashCode();  
  43.     }  

3、最终通知AfterAdvice
最终通知AfterAdvice的源码:
  1. public interface AfterAdvice extends Advice {  
  2. }  
和前置通知一样,最终通知也是继承了AOP联盟定义的Advice接口。
最终通知的子接口后置通知AfterReturningAdvice: 最终通知的继承体系中,后置通知AfterReturningAdvice接口继承了最终通知接口,源码如下:
  1. public interface AfterReturningAdvice extends AfterAdvice {  
  2.     //后置通知的回调方法,在目标方法对象调用结束并成功返回之后调用  
  3.     // returnValue参数为目标方法对象的返回值,method参数为目标方法对象,args为  
  4.     //目标方法对象的输入参数  
  5.     void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;  

后置通知的实现类CountingAfterReturningAdvice: 后置通知接口AfterReturningAdvice的实现类也比较多,以CountingAfterReturningAdvice类为例,分析后置通知的具体操作:

  1. public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {  
  2.     //实现后置通知AfterReturningAdvice的回调方法  
  3.     public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {  
  4.         //调用父类MethodCounter的count方法,统计方法的调用次数  
  5.         count(m);  
  6.     }  

4、例外通知ThrowAdvice
例外通知ThrowAdvice的源码
例外通知ThrowAdvice接口也继承自最终通知AfterAdvice,其源码如下:
  1. public interface ThrowsAdvice extends AfterAdvice {  

例外通知实现类CountingThrowsAdvice: 例外通知并没有指定需要实现的接口方法,它在抛出异常时被回调,这个回调是AOP利用JDK的反射机制来完成的,以其实现类CountingThrowsAdvice为例分析例外通知的用法,源码如下:
  1. public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {  
  2.         //当抛出IO类型的异常时的回调方法,统计异常被调用的次数  
  3.         public void afterThrowing(IOException ex) throws Throwable {  
  4.             count(IOException.class.getName());  
  5.         }  
  6.         //当抛出UncheckedException类型异常时的回调方法,统计异常被调用的次数  
  7.         public void afterThrowing(UncheckedException ex) throws Throwable {  
  8.             count(UncheckedException.class.getName());  
  9.         }  
  10.  }

Pointcut切入点:Pointcut切入点决定 通知Advice应该作用于哪个连接点,即通过Pointcut切入点来定义目标对象中需要使用AOP增强的方法集合,这些集合的选取可以按照一定的规则来完成。 Pointcut通常意味着标识方法,这些需要增强的方法可以被某个正则表达式进行标识,或者根据指定方法名进行匹配等。
Pointcut切入点源码:
  1. public interface Pointcut {  
  2.     //获取类过滤器  
  3.     ClassFilter getClassFilter();  
  4.     //获取匹配切入点的方法      
  5.     MethodMatcher getMethodMatcher();  
  6.     //总匹配的标准切入点实例  
  7.     Pointcut TRUE = TruePointcut.INSTANCE;  
  8. }
Pointcut接口的实现类JdkRegexpMethodPointcut: 查看Pointcut切入点的继承体系,发现Pointcut切入点的实现类非常的多,如针对注解配置的AnnotationMatchingPointcut、针对正则表达式的JdkRegexpMethodPointcut等等,以JdkRegexpMethodPointcut为例,分析切入点匹配的具体实现,源码如下:
  1. public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {  
  2.     //要编译的正则表达式模式  
  3.     private Pattern[] compiledPatterns = new Pattern[0];  
  4.     //编译时要排除的正则表达式模式  
  5.     private Pattern[] compiledExclusionPatterns = new Pattern[0];  
  6.    //将给定的模式字符串数组初始化为编译的正则表达式模式  
  7.     protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {  
  8.         this.compiledPatterns = compilePatterns(patterns);  
  9.     }  
  10.     //将给定的模式字符串数组初始化为编译时要排除的正则表达式模式  
  11.     protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {  
  12.         this.compiledExclusionPatterns = compilePatterns(excludedPatterns);  
  13.     }  
  14.     //使用正则表达式匹配给定的名称  
  15.     protected boolean matches(String pattern, int patternIndex) {  
  16.         Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);  
  17.         return matcher.matches();  
  18.     }  
  19.     //使用要排除的正则表达式匹配给定的名称  
  20.     protected boolean matchesExclusion(String candidate, int patternIndex) {  
  21.         Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);  
  22.         return matcher.matches();  
  23.     }  
  24.     //将给定的字符串数组编译为正则表达的模式  
  25.     private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {  
  26.         Pattern[] destination = new Pattern[source.length];  
  27.         for (int i = 0; i < source.length; i++) {  
  28.             destination[i] = Pattern.compile(source[i]);  
  29.         }  
  30.         return destination;  
  31.     }  
  32. }  
从上面的源码分析中,我们可以看到,最简单的使用正则表达式匹配的Pointcut切入点基本功能就是根据正则表达式判断方法名等是否匹配。

Advisor通知器
当完成对目标对象方法的增强行为操作(Advice)和切入点Point的设计开发之后,需要一个对象将目标对象、增强行为和切入点三者结合起来,通知器Advisor就是一个实现这个功能的对象,即通过Advisor通知器,可以定义那些目标对象的那些方法在什么地方使用这些增加的行为。
Advisor通知器: Advisor通知器的源码如下:
  1. public interface Advisor {  
  2.     //获取切面的通知Advice  
  3.     Advice getAdvice();  
  4.     //判断这个通知是否和某个特定的实例对象相关  
  5.     boolean isPerInstance();  
  6. }  
Advisor通知器的实现类DefaultPointcutAdvisor: 查看Advisor通知器的继承体系,发现Advisor的实现类很多,以最常用的DefaultPointcutAdvisor为例,分析通知器的工作原理。 a. DefaultPointcutAdvisor源码如下:
  1. public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {  
  2.     //默认切入点  
  3.     //Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;  
  4.     private Pointcut pointcut = Pointcut.TRUE;  
  5.     //无参构造方法,创建一个空的通知器  
  6.     public DefaultPointcutAdvisor() {  
  7.     }  
  8.     //创建一个匹配所有方法的通知器  
  9.     public DefaultPointcutAdvisor(Advice advice) {  
  10.         this(Pointcut.TRUE, advice);  
  11.     }  
  12.     //创建一个指定切入点和通知的通知器  
  13.     public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {  
  14.         this.pointcut = pointcut;  
  15.         setAdvice(advice);  
  16.     }  
  17.     //为通知设置切入点  
  18.     public void setPointcut(Pointcut pointcut) {  
  19.         this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);  
  20.     }  
  21.     //获取切入点  
  22.     public Pointcut getPointcut() {  
  23.         return this.pointcut;  
  24.     }  
  25.     public String toString() {  
  26.         return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";  
  27.     }  
  28. }  
上述源码中,通知器的默认切入点是Pointcut.TRUE,Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;

TruePointcut: TruePointcut作用通知器默认的切入点,其主要功能是配置默认的类过滤器和方法匹配器,即定义Spring AOP对于哪些类的哪些方法其作用,源码如下:

  1. class TruePointcut implements Pointcut, Serializable {  
  2.     //INSTANCE是TruePointcut类的一个常量单件,即整个应用中只有这个一个,  
  3.     //不会创建第二个实例对象,确保该实例对象的唯一性,单态模型  
  4.     public static final TruePointcut INSTANCE = new TruePointcut();  
  5.     //单态模式构造方法  
  6.     private TruePointcut() {  
  7.     }  
  8.     //获取切入点的类过滤器  
  9.     public ClassFilter getClassFilter() {  
  10.         return ClassFilter.TRUE;  
  11.     }  
  12.     //获取切入点的方法匹配器  
  13.     public MethodMatcher getMethodMatcher() {  
  14.         return MethodMatcher.TRUE;  
  15.     }  
  16.     //获取单态模式对象的方法  
  17.     private Object readResolve() {  
  18.         return INSTANCE;  
  19.     }  
  20.     public String toString() {  
  21.         return "Pointcut.TRUE";  
  22.     }  

从TruePointcut的源码我们看到,切入点使用TrueClassFilter作为类过滤器,匹配任意的类,使用TrueMethodMatcher作为方法匹配器,匹配任意的方法。下面我们继续分析TrueClassFilter类过滤器和TrueMethodMatcher方法匹配器。

TrueClassFilter: TrueClassFilter作为默认切入点的默认类过滤器,主要告诉切入点对哪些类进行增强,源码如下:

  1. class TrueClassFilter implements ClassFilter, Serializable {  
  2.     //单态模式  
  3.     public static final TrueClassFilter INSTANCE = new TrueClassFilter();  
  4.     //单态模式的构造方法  
  5.     private TrueClassFilter() {  
  6.     }  
  7.     //切入点过滤匹配类的方法,默认对所有的类都增强  
  8.     public boolean matches(Class clazz) {  
  9.         return true;  
  10.     }  
  11.     //获取单态模式对象的方法  
  12.     private Object readResolve() {  
  13.         return INSTANCE;  
  14.     }  
  15.     public String toString() {  
  16.         return "ClassFilter.TRUE";  
  17.     }  
  18. }  
  TrueMethodMatcher: TrueMethodMatcher作为默认切入点的默认方法匹配器,主要告诉切入点对哪些方法进行增强,源码如下:
  1. class TrueMethodMatcher implements MethodMatcher, Serializable {  
  2.     //单态模式  
  3.     public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();  
  4.     //单态模式构造方法  
  5.     private TrueMethodMatcher() {  
  6.     }  
  7.     //不支持运行时调用  
  8.     public boolean isRuntime() {  
  9.         return false;  
  10.     }  
  11.     //切入点匹配方法时调用,默认匹配所有的方法  
  12.     public boolean matches(Method method, Class targetClass) {  
  13.         return true;  
  14.     }  
  15.     //运行时调用将抛出异常  
  16.     public boolean matches(Method method, Class targetClass, Object[] args) {  
  17.         throw new UnsupportedOperationException();  
  18.     }  
  19.     //获取单态模式对象的方法  
  20.     private Object readResolve() {  
  21.         return INSTANCE;  
  22.     }  
  23.     public String toString() {  
  24.         return "MethodMatcher.TRUE";  
  25.     }  
  26. }  
从上面方法匹配器的源码,我们可以看出,切入点对方法进行匹配时不支持运行时的匹配,如果在运行时进行匹配将抛出异常。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多