Spring AOP 增强框架 Nepxion Matrix 详解

文章来源公众号:方志朋
在《深入聊一聊 Spring AOP 实现机制》一文中,介绍了 Spring AOP 的多种实现机制,原理大同小异。本篇来继续介绍一款开源的 AOP 框架:Nepxion Matrix,该框架致力于对 Spring AOP 的扩展和增强,灵活而且易用。

GitHub:https://github.com/Nepxion/Matrix

Matrix 框架主要对 Spring 做了三个模块的扩展:Spring AutoProxy,Spring Registrar,Spring Selectror。
本篇主要分析 AOP相关的功能,也就是 AutoProxy 模块。主要围绕以下几个方面:

  • Nepxion Matrix AutoProxy框架有什么特性?

  • Nepxion Matrix AutoProxyAOP 增强框架的扩展点什么?如何扩展?

  • 源码分析。

  • 该框架和Spring AOP异同点。


一:Nepxion Matrix AutoProxy特性

大多数的项目中,使用Spring AOP的方式都是采用注解形式,方法上面加个自定义注解即可实现,这种方式注解只能加在类方法上,不能加在接口或接口方法上。Nepxion Matrix AutoProxy主要针对这些问题,特性如下:

  • 支持通用代理和额外代理

  • 支持接口代理和类代理

  • 支持接口方法代理

这里要介绍一下上面提到了两种代理方式:

通用代理是指通过AbstractAutoProxyCreator中的变量interceptorNames,来设置具体的通知名称。 ****额外代理是指通过实现自定义逻辑,来选择性设置通知(这里的通知也就是拦截方法)。


二:Nepxion Matrix AutoProxy扩展点

要理解该框架的实现方式,首先要知道该框架的扩展点是什么。

先来看一下代理机制相关的 UML 图:

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; font-size: 0.7em; color: rgb(153, 153, 153); line-height: inherit; text-align: center;">AbstractAutoProxyCreator抽象类为Spring AOP暴露的抽象扩展类,其每一个实现类都代表着一种实现机制。Nepxion Matrix也正是基于此类做为扩展点,分别来看一下涉及到核心类:
</figcaption>

  • AbstractAutoScanProxyNepxion Matrix提供的核心抽象类,封装了获取顾问advisor的方法,并暴露了一些抽象方法,如获取通知,注解等方法。该类同 Spring 内置的代理机制AbstractAdvisorAutoProxyCreator平级,默认先执行Spring AOP内置代理机制。

  • DefaultAutoScanProxy:提供了一些默认为空的实现,不能直接使用。

  • MyAutoScanProxyForClass:类代理机制,提供通用代理实现。

  • MyAutoScanProxyForMethod:方法代理机制,提供额外代理。

  • MyAutoScanProxy:混合代理,提供通用代理和额外代理。


三:源码分析

这里就针对类代理的方式,进行源码分析。先来看源码中的使用示例:


@MyAnnotation1(name = "MyAnnotation1", label = "MyAnnotation1", description = "MyAnnotation1")
public interface MyService1 {
    void doA(String id);

    void doB(String id);
}
@Service
public class MyService1Impl implements MyService1 {
    @Override
    public void doA(String id) {
        System.out.println("doA");
    }

    @Override
    public void doB(String id) {
        System.out.println("doB");
    }
}

示例中只需在接口上添加一个自定义注解@MyAnnotation1,即可满足两个实现方法都会走代理方法。

源码中还有其他几种使用示例,这里就不列举了,具体可以参考项目wiki。


首先来看一下AbstractAutoScanProxy的构造方法:

 public AbstractAutoScanProxy(String[] scanPackages, ProxyMode proxyMode, ScanMode scanMode, boolean exposeProxy) {
     //设置代理目录,非指定目录直接返回
        this.scanPackages = scanPackages;
        //Spring提供的是否暴露代理对象标识。
        this.setExposeProxy(exposeProxy);
        //代理模式,类代理或是方法代理。
        this.proxyMode = proxyMode;
        this.scanMode = scanMode;

     //......

        // 设定全局拦截器,通过名称指定。
        // 如果同时设置了全局和额外的拦截器,那么它们都同时工作,全局拦截器先运行,额外拦截器后运行
        Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
        String[] commonInterceptorNames = getCommonInterceptorNames();

        String[] interceptorNames = ArrayUtils.addAll(commonInterceptorNames, convertInterceptorNames(commonInterceptorClasses));
        if (ArrayUtils.isNotEmpty(interceptorNames)) {
            setInterceptorNames(interceptorNames);
        }
    }

构造方法中有两个变量比较重要:
**exposeProxy:默认为false,这里设置为 true,支持在同一个类中,一个方法调用另一个方法走代理拦截方法。 比如,类中方法1调用方法2,开启该变量,则不会直接调用方法2,而是从 threadLocal 中取出提前存入的代理类发起调用。 **

interceptorNames:通知名称,也就是通用代理,通过构造方法设置。在后面生成代理类的方法中会根据该变量取出所有拦截器实例。

我们来看一下代理执行入口。因为该类继承beanPostProcessor,所以最终会执行扩展接口postProcessAfterInitialization,在该方法中调用模板方法getAdvicesAndAdvisorsForBean,来看一下Nepxion Matrix对该方法的实现:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
        boolean scanPackagesEnabled = scanPackagesEnabled();
        // scanPackagesEnabled=false,表示“只扫描指定目录”的方式未开启,则不会对扫描到的bean进行代理预先判断
        if (scanPackagesEnabled) {
            boolean scanPackagesContained = scanPackagesContained(beanClass);
            // 如果beanClass的类路径,未包含在扫描目录中,返回DO_NOT_PROXY
            if (!scanPackagesContained) {
                return DO_NOT_PROXY;
            }
        }

        // 根据Bean名称获取Bean对象
        Object bean = beanMap.get(beanName);

        // 获取最终目标类,
        Class<?> targetClass = null;
        if (bean != null /* && AopUtils.isCglibProxy(bean) */) {
            targetClass = AopProxyUtils.ultimateTargetClass(bean);
        } else {
            targetClass = beanClass;
        }

        // Spring容器扫描实现类
        if (!targetClass.isInterface()) {
            // 扫描接口(从实现类找到它的所有接口)
            if (targetClass.getInterfaces() != null) {
                for (Class<?> targetInterface : targetClass.getInterfaces()) {
                    Object[] proxyInterceptors = scanAndProxyForTarget(targetInterface, beanName, false);
                    if (proxyInterceptors != DO_NOT_PROXY) {
                        return proxyInterceptors;
                    }
                }
            }

            // 扫描实现类(如果接口上没找到注解, 就找实现类的注解)
            Object[] proxyInterceptors = scanAndProxyForTarget(targetClass, beanName, true);
            if (proxyInterceptors != DO_NOT_PROXY) {
                return proxyInterceptors;
            }
        }
        return DO_NOT_PROXY;
    }

上面逻辑中调用了AopProxyUtils.ultimateTargetClass(bean)来获取对应的 class 对象,而不是使用参数中的beanClass。因为方法传进来的 class 对象有可能是被代理过的 class,所以这里要获取最初的 class 对象。

继续跟进scanAndProxyForTarget方法:

 protected Object[] scanAndProxyForTarget(Class<?> targetClass, String beanName, boolean proxyTargetClass) {
        String targetClassName = targetClass.getCanonicalName();
        //这里获取额外代理
        Object[] interceptors = getInterceptors(targetClass);
        // 排除java开头的接口,例如java.io.Serializable,java.io.Closeable等,执行不被代理
        if (StringUtils.isNotEmpty(targetClassName) && !targetClassName.startsWith("java.")) {
            // 避免对同一个接口或者类扫描多次
            Boolean proxied = proxyMap.get(targetClassName);
            if (proxied != null) {
                if (proxied) {
                    return interceptors;
                }
            } else {
                Object[] proxyInterceptors = null;
                switch (proxyMode) {
                    // 只通过扫描到接口名或者类名上的注解后,来确定是否要代理
                    case BY_CLASS_ANNOTATION_ONLY:
                        proxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        break;
                    // 只通过扫描到接口或者类方法上的注解后,来确定是否要代理
                    case BY_METHOD_ANNOTATION_ONLY:
                        proxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        break;
                    // 上述两者都可以
                    case BY_CLASS_OR_METHOD_ANNOTATION:
                        Object[] classProxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        // 没有接口或者类名上扫描到目标注解,那么扫描接口或者类的方法上的目标注解
                        Object[] methodProxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        if (classProxyInterceptors != DO_NOT_PROXY || methodProxyInterceptors != DO_NOT_PROXY) {
                            proxyInterceptors = interceptors;
                        } else {
                            proxyInterceptors = DO_NOT_PROXY;
                        }
                        break;
                }

                // 是否需要代理
                proxyMap.put(targetClassName, Boolean.valueOf(proxyInterceptors != DO_NOT_PROXY));
                return proxyInterceptors;
            }
        }
        return DO_NOT_PROXY;
    }

大致的思路:根据MyService1Impl获取到接口MyService1,然后判断接口上是否有指定的注解@MyAnnotation1,判断条件符合,然后调用getInterceptors方法获取拦截器,传递到父类AbstractAutoProxyCreator中的方法createProxy中,完成代理。

跟进getInterceptors方法来看一下:

protected Object[] getInterceptors(Class<?> targetClass) {
        Object[] interceptors = getAdditionalInterceptors(targetClass);
        if (ArrayUtils.isNotEmpty(interceptors)) {
            return interceptors;
        }

        Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
        String[] commonInterceptorNames = getCommonInterceptorNames();
        if (ArrayUtils.isNotEmpty(commonInterceptorClasses) || ArrayUtils.isNotEmpty(commonInterceptorNames)) {
            return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
        }

        return DO_NOT_PROXY;
    }
这里先获取

这里先获取所有的额外代理拦截器,如果有直接返回。如果为空,则返回一个是否有通用代理拦截器的标识,具体拦截器的名称上面已经通过构造方法传入。

再来看一下在createProxy方法:

   protected Object createProxy(
            Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);

        //判断代理生成方式
        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            }
            else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        //获取拦截器,包括通用代理和额外代理
        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

        return proxyFactory.getProxy(getProxyClassLoader());
    }

Nepxion Matrix重写了上面的shouldProxyTargetClass(beanClass, beanName)方法,重写逻辑如下:

 protected boolean shouldProxyTargetClass(Class<?> beanClass, String beanName) {
        // 设置不同场景下的接口代理,还是类代理
        Boolean proxyTargetClass = proxyTargetClassMap.get(beanName);
        if (proxyTargetClass != null) {
            return proxyTargetClass;
        }
        return super.shouldProxyTargetClass(beanClass, beanName);
    }

需要注意的是,上述重写方式只在SpringBoot 1.x中生效,因为在 2.x版本中,proxyFactory.isProxyTargetClass()默认为 true,默认走 cglib 代理,所以默认情况下上述重写的方法不会执行。

继续跟进获取拦截器的方法buildAdvisors

protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
        //解析通用代理拦截器
        Advisor[] commonInterceptors = resolveInterceptorNames();

        List<Object> allInterceptors = new ArrayList<Object>();
        //判断是否需要额外代理,是否有指定拦截器
        if (specificInterceptors != null) {
            allInterceptors.addAll(Arrays.asList(specificInterceptors));
            if (commonInterceptors.length > 0) {
                if (this.applyCommonInterceptorsFirst) {
                    allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
                }
                else {
                    allInterceptors.addAll(Arrays.asList(commonInterceptors));
                }
            }
        }
        Advisor[] advisors = new Advisor[allInterceptors.size()];
        for (int i = 0; i < allInterceptors.size(); i++) {
            advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
        }
        return advisors;
    }

通过调用resolveInterceptorNames,根据interceptorNames中设置的拦截器名称,从Spring容器中取出所有的通用代理拦截器,结合指定拦截器specificInterceptors,一起织入代理类。

Nepxion Matrix AutoProxy中的方法代理这里就不展开了,原理类似。


四:Nepxion Matrix AutoProxy 和 Spring AOP 异同点

1)代理机制原理一样,都是AbstractAutoScanProxy的实现类,只是代理功能点不同。

2)两种代理机制可同时使用。如果同时使用,一定保证Spring AOP先代理,Nepxion Matrix AutoProxy后代理。这也是默认的代理顺序。尽量不要通过重写Ordered接口的方式改变先后顺序。

原因是采用Spring AOP注解形式时需要获取代理类最初的 Class 对象,如果Nepxion Matrix AutoProxy先执行,那么在执行Spring AOP代理逻辑时获取到的当前 Class 对象就是被代理过重新生成的 Class 对象,这时就无法获取自定义的切面注解了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容