1. 程式人生 > >Spring AOP 增強框架 Nepxion Matrix 詳解

Spring AOP 增強框架 Nepxion Matrix 詳解

概述

本篇來繼續介紹一款開源的 AOP 框架:Nepxion Matrix,該框架致力於對 Spring AOP 的擴充套件和增強,靈活而且易用。

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 圖:

 

AbstractAutoProxyCreator抽象類為Spring AOP暴露的抽象擴充套件類,其每一個實現類都代表著一種實現機制。Nepxion Matrix也正是基於此類做為擴充套件點,分別來看一下涉及到核心類:

 

  • AbstractAutoScanProxy

    Nepxion 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 物件。

推薦一個Java進階架構學習交流:952124565,群內有分散式架構、高效能、高併發、效能優化、Spring boot、Redis、ActiveMQ、Nginx、Netty、Jvm等視訊資料提供學習參考。

繼續跟進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 物件,這時就無法獲取自定義的切面註解了。


歡迎Java工程師朋友們加入Java進階架構學習交流:952124565

本群提供免費的學習指導 架構資料 以及解答,不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導。