zoukankan      html  css  js  c++  java
  • Spring技术内幕:Spring AOP的实现原理(一)

    一、SpringAOP的概述
    1、AOP概念
    AOP是Aspect-Oriented Programming(面向切面编程)的简称。维基百科的解释例如以下:
    Aspect是一种新的模块化机制,用来描写叙述分散在对象、类或函数中的横切关注点(crosscutting concern)。

    从关注点中分离出横切关注点是面向切面的程序设计的核心。

    分离关注点使解决特定领域问题的代码从业务逻辑代码中独立出来,业务逻辑的代码中不再含有针对特定领用问题代码的调用。业务逻辑同特定领域问题的关系通过切面来封装、维护,这样原本分散在整个应用程序中的变动就能够非常好的管理起来。
    2、Advice通知
    Advice定义在连接点为切面增强提供织入接口。

    在Spring AOP中。他主要描写叙述Spring AOP环绕方法调用而注入的切面行为。Advice是定义在org.aopalliance.aop.Advice中的接口。在Spring AOP使用这个统一接口。并通过这个接口为AOP切面增强的织入功能做了很多其它的细节和扩展,比方提供了更详细的通知类型,如BeforeAdvice,AfterAdvice,ThrowsAdvice等。
    2.1 BeforeAdvice
    首先我们从BeforeAdvice開始:
    在BeforeAdvice的继承关系中,定义了为待增强的目标方法设置的前置增强接口MethodBeforeAdvice,使用这个前置接口须要实现一个回调函数:

    void before(Method method,Object[] args,Object target) throws Throwable;

    作为回调函数,before方法的实如今Advice中被配置到目标方法后,会在调用目标方法时被回调。详细的參数有:Method对象。这个參数是目标方法的反射对象;Object[]对象数组,这个对象数组中包括目标方法的输入參数。

    以CountingBeforeAdvice为例来说明BeforeAdvice的详细使用,CountBeforeAdvice是接口MethodBeforeAdvice的详细实现。他仅仅是统计被调用方法的次数,作为切面增强实现。他会依据调用方法的方法名进行统计,把统计结果依据方法名和调用次数作为键值对放入一个map中。代码例如以下:

    public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {
        //实现方法前置通知MethodBeforeAdvice接口的方法
        public void before(Method m, Object[] args, Object target) throws Throwable {
        //以目标对象方法作为參数,调用父类MethodCounter的count方法统计方法调用次数
            count(m);
        }
    }
    CountingBeforeAdvice的父类MethodCounter的源代码例如以下:
    public class MethodCounter implements Serializable {
        //方法名—>方法调用次数的map集合,存储方法的调用次数
        private HashMap<String, Integer> map = new HashMap<String, Integer>();
        //全部方法的总调用次数
        private int allCount;
        //统计方法调用次数,CountingBeforeAdvice的调用入口
        protected void count(Method m) {
            count(m.getName());
        }
        //统计指定名称方法的调用次数
        protected void count(String methodName) {
            //从方法名—>方法调用次数集合中获取指定名称方法的调用次数
            Integer i = map.get(methodName);
    //假设调用次数不为null,则将调用次数加1,假设调用次数为null,则设置调用次数为1
            i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);
            //又一次设置集合中保存的方法调用次数
            map.put(methodName, i);
            //全部方法总调用次数加1
            ++allCount;
        }
        //获取指定名称方法的调用次数
        public int getCalls(String methodName) {
            Integer i = map.get(methodName);
            return (i != null ? i.intValue() : 0);
        }
        //获取全部方法的总调用次数
        public int getCalls() {
            return allCount;
        }
        public boolean equals(Object other) {
            return (other != null && other.getClass() == this.getClass());
        }
        public int hashCode() {
            return getClass().hashCode();
        }
    }

    2.2 AfterAdvice
    在Advice的实现体系中,Spring还提供了AfterAdvice这样的通知类型,这里以AfterReturningAdvice通知的实现为例,代码例如以下:

    public interface AfterReturningAdvice extends AfterAdvice {
    //后置通知的回调方法。在目标方法对象调用结束并成功返回之后调用
    // returnValue參数为目标方法对象的返回值。method參数为目标方法对象,args为
        //目标方法对象的输入參数
        void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
    }

    afterReturning方法也是一个回调函数。AOP应用须要在这个接口实现中提供切面增强的详细设计,在这个Advice通知被正确配置以后,在目标方法调用结束并成功返回的时候,接口会被SpringAOP调用。与前面分析的一样,在Spring AOP包中,相同能够看到CountingAfterReturningAdvice。实现基本一致:

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

    在实现AfterReturningAdvice的接口方法afterReturning中。能够调用MethodCounter的count方法,从而完毕依据方法名对目标方法调用次数的统计。
    2.3 ThrowsAdvice
    以下我们来看一下Advice通知的还有一种类型ThrowsAdvice。

    对于ThrowsAdvice,并没有制定须要实现的接口方法,他在抛出异常时被回调。这个回调是AOP使用反射机制来完毕的。能够通过CountingThrowsAdvice来了解ThrowsAdvice的用法:

    public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {
            //当抛出IO类型的异常时的回调方法,统计异常被调用的次数
            public void afterThrowing(IOException ex) throws Throwable {
                count(IOException.class.getName());
            }
            //当抛出UncheckedException类型异常时的回调方法,统计异常被调用的次数
            public void afterThrowing(UncheckedException ex) throws Throwable {
                count(UncheckedException.class.getName());
            }
        }

    3、Pointcut切点
    决定Advice通知应该作用于哪个连接点。也就是说通过Pointcut来定义须要增强的方法的集合,这些集合的选取能够依照一定的规则来完毕。Pointcut通常意味着标识方法,比如,这些须要增强的地方能够由某个正則表達式进行标识,或依据某个方法名进行匹配。源代码例如以下:

    public interface Pointcut {
        //获取类过滤器
        ClassFilter getClassFilter();
        //获取匹配切入点的方法    
        MethodMatcher getMethodMatcher();
        //总匹配的标准切入点实例
        Pointcut TRUE = TruePointcut.INSTANCE;
    } 

    查看Pointcut切入点的继承体系,发现Pointcut切入点的实现类非常的多,如针对注解配置的AnnotationMatchingPointcut、针对正則表達式的JdkRegexpMethodPointcut等等,我们以JdkRegexpMethodPointcut为例,分析切入点匹配的详细实现。源代码例如以下:

    public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {
        //要编译的正則表達式模式
        private Pattern[] compiledPatterns = new Pattern[0];
        //编译时要排除的正則表達式模式
        private Pattern[] compiledExclusionPatterns = new Pattern[0];
        //将给定的模式字符串数组初始化为编译的正則表達式模式
        protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
            this.compiledPatterns = compilePatterns(patterns);
        }
        //将给定的模式字符串数组初始化为编译时要排除的正則表達式模式
        protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
            this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
        }
        //使用正則表達式匹配给定的名称
        protected boolean matches(String pattern, int patternIndex) {
            Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
            return matcher.matches();
        }
        //使用要排除的正則表達式匹配给定的名称
        protected boolean matchesExclusion(String candidate, int patternIndex) {
            Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);
            return matcher.matches();
        }
        //将给定的字符串数组编译为正则表达的模式
        private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
            Pattern[] destination = new Pattern[source.length];
            for (int i = 0; i < source.length; i++) {
                destination[i] = Pattern.compile(source[i]);
            }
            return destination;
        }
    }

    4、Advisor通知器
    完毕对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)以后。须要一个对象把他们结合起来,完毕这个作用的就是Advisor。

    通过他能够定义应该使用哪个通知并在哪个关注点使用它。在DefaultPointcutAdvisor中有两个属性。各自是advice和Pointcut。通过这两个属性,能够分别配置Advice和Pointcut。源代码例如以下:

    public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
        //默认切入点
        //Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;
        private Pointcut pointcut = Pointcut.TRUE;
        //无參构造方法。创建一个空的通知器
        public DefaultPointcutAdvisor() {
        }
        //创建一个匹配全部方法的通知器
        public DefaultPointcutAdvisor(Advice advice) {
            this(Pointcut.TRUE, advice);
        }
        //创建一个指定切入点和通知的通知器
        public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
            this.pointcut = pointcut;
            setAdvice(advice);
        }
        //为通知设置切入点
        public void setPointcut(Pointcut pointcut) {
            this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
        }
        //获取切入点
        public Pointcut getPointcut() {
            return this.pointcut;
        }
        public String toString() {
            return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
        }
    }

    上述源代码中,通知器的默认切入点是Pointcut.TRUE,Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE
    TruePointcut的INSTANCE是一个单件。比方使用static类变量来持有单件实例,使用private私有构造函数来确保除了在当前单件实现中。单件不会被再次创建和实例化。


    TruePointcut和TrueMethodMatcher的实现如代码例如以下:

    /**
     * Canonical Pointcut instance that always matches.
     *
     * @author Rod Johnson
     */
    @SuppressWarnings("serial")
    class TruePointcut implements Pointcut, Serializable {
        public static final TruePointcut INSTANCE = new TruePointcut();
    
        /**
         * Enforce Singleton pattern.
         * 这里是单件模式的实现特点,设置私有构造函数,使其不能直接被实例化
         * 并设置一个静态类变量来保证该实例是唯一的
         */
        private TruePointcut() {
        }
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }
        public MethodMatcher getMethodMatcher() {
            return MethodMatcher.TRUE;
        }
        /**
         * Required to support serialization. Replaces with canonical
         * instance on deserialization, protecting Singleton pattern.
         * Alternative to overriding {@code equals()}.
         */
        private Object readResolve() {
            return INSTANCE;
        }
        @Override
        public String toString() {
            return "Pointcut.TRUE";
        }
    }
    /**
     * Canonical MethodMatcher instance that matches all methods.
     *
     * @author Rod Johnson
     */
    @SuppressWarnings("serial")
    class TrueMethodMatcher implements MethodMatcher, Serializable {
        public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
        /**
         * Enforce Singleton pattern.
         */
        private TrueMethodMatcher() {
        }
        public boolean isRuntime() {
            return false;
        }
        public boolean matches(Method method, Class targetClass) {
            return true;
        }
        public boolean matches(Method method, Class targetClass, Object[] args) {
            // Should never be invoked as isRuntime returns false.
            throw new UnsupportedOperationException();
        }
        /**
         * Required to support serialization. Replaces with canonical
         * instance on deserialization, protecting Singleton pattern.
         * Alternative to overriding {@code equals()}.
         */
        private Object readResolve() {
            return INSTANCE;
        }
        @Override
        public String toString() {
            return "MethodMatcher.TRUE";
        }
    }

    下一篇我们開始一起学习Spring AOP究竟是什么实现的
    未完待续……

  • 相关阅读:
    hadoop(四)HDFS的核心设计
    spark(三)spark sql
    hadoop(三)HDFS基础使用
    hadoop(二)hadoop集群的搭建
    IOS微信中看文章跳转页面后点击返回无效
    使用GBK编码请求访问nodejs程序报415错误:Error: unsupported charset at urlencodedParser ...
    mac端口占用查找进程并杀死
    tinymce4.x 上传本地图片(自己写个插件)
    mongodb使用mongoose分组查询
    javascript/jquery给动态加载的元素添加click事件
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5129114.html
Copyright © 2011-2022 走看看