SPI框架實現之旅三:實現說明

SPI框架實現之旅三:實現說明

前一篇 《SPI框架實現之旅二:總體設計》中,介紹了幾個定義的接口,註解;敘述了實現流程;並簡單的介紹了 SpiLoader中的部分實現; 本篇則主要介紹SpiLoader類的實現java

類圖結構以下:git

https://static.oschina.net/uploads/img/201705/27183336_TOny.png

SpiLoader 全解析

spiImpl選擇的核心類,包括初始化選擇器,初始化spiImpl實現列表,解析spiImpl的選擇條件,返回具體的實現類等G緩存

1. 獲取spiLoader對象

SpiLoader 是一個泛型對象,每一個SPI接口,對應一個SpiLoader<T> 對象,咱們提供了一個靜態方法來獲取這個對象安全

實現

優先從緩存中獲取, 若是緩存沒有,則新建一個;緩存中有, 則直接返回app

/**
* spiLoader緩存, 其中key爲 spi接口, value爲對應的Loader對象
*/
private static final ConcurrentMap<Class<?>, SpiLoader<?>> loaderCache = new ConcurrentHashMap<>();


@SuppressWarnings("unchecked")
public static <T> SpiLoader<T> load(Class<T> type) {
   if (null == type) {
       throw new IllegalArgumentException("common cannot be null...");
   }

   if (!type.isInterface()) {
       throw new IllegalArgumentException("common class:" + type + " must be interface!");
   }


   if (!withSpiAnnotation(type)) {
       throw new IllegalArgumentException("common class:" + type + " must have the annotation of @Spi");
   }


   SpiLoader<T> spiLoader = (SpiLoader<T>) loaderCache.get(type);
   if (spiLoader == null) {
       loaderCache.putIfAbsent(type, new SpiLoader<>(type));
       spiLoader = (SpiLoader<T>) loaderCache.get(type);
   }

   return spiLoader;
}

說明

  • 上面有幾個校驗,前一篇已經說明,再也不贅述
  • 上面新建對象,不是線程安全的

2. 新建 SpiLoader對象

建立對象,主要會初始化選擇器框架

實現

private SpiLoader(Class<T> type) {
   // 初始化默認的選擇器, 爲保留項目, 必然會提供的服務
   selectorInstanceCacheMap.putIfAbsent(DefaultSelector.class, DEFAULT_SELECTOR);

   this.spiInterfaceType = type;
   initSelector();
}


private void initSelector() {
   Spi ano = spiInterfaceType.getAnnotation(Spi.class);
   if (ano == null) {
       currentSelector = initSelector(DefaultSelector.class);
   } else {
       currentSelector = initSelector(ano.selector());
   }


   Method[] methods = this.spiInterfaceType.getMethods();
   currentMethodSelector = new ConcurrentHashMap<>();
   SelectorWrapper temp;
   for (Method method : methods) {
       if (!method.isAnnotationPresent(SpiAdaptive.class)) {
           continue;
       }

       temp = initSelector(method.getAnnotation(SpiAdaptive.class).selector());
       if (temp == null) {
           continue;
       }

       currentMethodSelector.put(method.getName(), temp);
   }
}


private SelectorWrapper initSelector(Class<? extends ISelector> clz) {
   // 優先從選擇器緩存中獲取類型對應的選擇器
   if (selectorInstanceCacheMap.containsKey(clz)) {
       return selectorInstanceCacheMap.get(clz);
   }


   try {
       ISelector selector = clz.newInstance();
       Class paramClz = null;

       Type[] types = clz.getGenericInterfaces();
       for (Type t : types) {
           if (t instanceof ParameterizedType) {
               paramClz = (Class) ((ParameterizedType) t).getActualTypeArguments()[0];
               break;
           }
       }

       Assert.check(paramClz != null);
       SelectorWrapper wrapper = new SelectorWrapper(selector, paramClz);
       selectorInstanceCacheMap.putIfAbsent(clz, wrapper);
       return wrapper;
   } catch (Exception e) {
       throw new IllegalArgumentException("illegal selector defined! yous:" + clz);
   }
}

說明

  • 持有一個選擇器緩存列表,selectorInstanceCacheMap
    • 保證每種類型的選擇器,在這個SpiLoader中,只會有一個實例存在
    • 不作成全局惟一的緣由是儘可能隔離, 好比 ParamsSelector 內部緩存了spi實現的列表,若是全局公用的話,就會混掉,致使這個列表中就出現非這個spi接口的實現類
  • 類選擇器 + 方法選擇器
    • currentSelector : 類選擇器, 解析 @Spi 註解獲取,適用於靜態選擇 + 動態選擇兩種使用方式
    • currentMethodSelector : 方法選擇器,解析 @SpiAdaptive 註解獲取, 僅適用於動態選擇SPI實現的方式
    • 優先級: 方法上定義的選擇器 因爲 類上定義的選擇器; 方法上未定義時,默認使用類定義的選擇器

3. 靜態使用

靜態使用方式,表示根據傳入的條件,選擇一個知足條件的實現返回函數

實現

/**
* 根據傳入條件, 選擇具體的spi實現類
* <p/>
* 這裏要求conf的類型和選擇器的參數類型匹配, 不然會嘗試使用默認的選擇器補救, 若補救失敗, 則拋異常
*
* @param conf
* @return
* @throws NoSpiMatchException
* @throws IllegalArgumentException
*/
@SuppressWarnings("unchecked")
public T getService(Object conf) throws NoSpiMatchException {
   if (spiImplClassCacheMap == null || spiImplClassCacheMap.size() == 0) {
       loadSpiService();
   }

   if (!currentSelector.getConditionType().isAssignableFrom(conf.getClass())) {

       /**
        * 參數類型不匹配時, 判斷是否能夠根據默認的選擇器來獲取
        */
       if (conf instanceof String) {
           return (T) DEFAULT_SELECTOR.getSelector().selector(spiImplClassCacheMap, conf);
       }


       /**
        * 參數類型徹底不匹配, 則拋參數異常
        */
       throw new IllegalArgumentException("conf spiInterfaceType should be sub class of [" + currentSelector.getConditionType() + "] but yours:" + conf.getClass());
   }


   return (T) currentSelector.getSelector().selector(spiImplClassCacheMap, conf);
}

說明

  1. spiImplClassCacheMap性能

    spi實現的緩存映射表,優先判斷緩存映射表是否存在,不存在時須要初始化;存在時,則進入校驗邏輯測試

  2. 校驗ui

    校驗傳入的參數,是否匹配當前的選擇器參數類型,爲了保證選擇器能夠正常運行

    當不匹配時,會有一個兼容邏輯,判斷傳參類型是否爲String, 是則採用默認的選擇器,根據name來選擇spi實現 (這種實現可能形成選擇的實現不是預期的)

  3. 靜態使用方式,使用類定義選擇器 : currentSelector

    • 靜態使用的方式,目標就是事前就確認使用這個實現了,不會出現變更了; 至關於一次確認,全部的調用都是確認的

    • 靜態使用,方法註解的選擇器無效。這個咱們從逆向的思路進行解釋

      IPrint 是一個Spi接口, 有兩個實現   FilePrint,   ConsolePrint
        假設 `currentSelector=DefaultSelector`, 方法  methodA 上定義的是  ParamsSelector 時
      
        靜態使用方式,獲取一個spi實現,但願在全部的spi接口使用處,都輸出到文件,用戶根據 `FilePrint` 選擇  FilePrint 這個類來執行具體的輸出邏輯, 若是在調用 methodA 方法執行時, 假設根據  ParamsSelector 判斷, ConsolePrint 才知足這兒條件,這是至關於在具體實現時,換成了另外一個 ConsolePrint, 這下子就與咱們的初衷背離了(若是目標是想實現這個場景,顯然動態適配的方式纔是正確的使用姿式)
  4. loadService 的邏輯後面詳細說明

4. 動態使用

動態使用區別於靜態的直接肯定實現類, 經過getService 獲取的並非某個特定對的實現類,而是一個動態生成的代理,每次具體執行以前,會去判斷一下,應該選擇哪個實現來執行

設計的出發點

能夠考慮下,咱們的目標是在執行方法以前,須要判斷一下哪一個實現類知足要求,選擇這個實現類來執行這個方法,那麼咱們能夠怎麼去作?

考慮到切面的方式,若是有一種手段,在方法執行以前,織入一段業務邏輯,就能夠達到上面的目的

最開始雖然是怎麼想的,可是有點尷尬的是,不知道怎麼去實現;所以換了一個思路,我本身新生成一個接口的實現類,在這個實現類裏面作選擇邏輯,而後把這個實現類對象返回

實現以下

和靜態實現的邏輯差很少,通常流程以下:

  • 判斷spi實現類的映射關係表是否初始化,若沒有則初始化
  • 獲取選擇器
    • 優先從方法選擇器中查找, 若存在,則直接選中;
    • 不存在,則使用類選擇器
  • 校驗:判斷傳入條件參數類型是否知足選擇器的參數類型匹配(將方法的第一個參數,做爲選擇器的選擇條件)
  • 返回實現類
@SuppressWarnings("unchecked")
public T getService(Object conf, String methodName) throws NoSpiMatchException {
   if (spiImplClassCacheMap == null || spiImplClassCacheMap.size() == 0) {
       loadSpiService();
   }


   // 首先獲取對應的selector
   SelectorWrapper selector = currentMethodSelector.get(methodName);
   if (selector == null) { // 自適應方法上未定義選擇器, 則默認繼承類的
       selector = currentSelector;
       currentMethodSelector.putIfAbsent(methodName, selector);
   }

   if (!selector.getConditionType().isAssignableFrom(conf.getClass())) { // 選擇器類型校驗
       if (!(conf instanceof String)) {
           throw new IllegalArgumentException("conf spiInterfaceType should be sub class of [" + currentSelector.getConditionType() + "] but yours:" + conf.getClass());
       }


       selector = DEFAULT_SELECTOR;
   }


   if (spiImplMethodCacheMap.size() == 0) {
       return (T) selector.getSelector().selector(spiImplClassCacheMap, conf);
   }


   try {
       // 採用默認的選擇器,根據指定name 進行查詢時, 須要兼容一下, 由於method對應的緩存key爲  SpiImpName_methodName
       if (DEFAULT_SELECTOR.equals(selector)) {
           if (spiImplMethodCacheMap.containsKey(conf)) {
               return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf);
           }


           if (spiImplClassCacheMap.containsKey(conf)) {
               return (T) selector.getSelector().selector(spiImplClassCacheMap, conf);
           }


           return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf + "_" + methodName);
       } else {
           return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf);
       }
   } catch (Exception e) {
       return (T) selector.getSelector().selector(spiImplClassCacheMap, conf);
   }
}

說明

  1. 這個方法一般是由框架生成的代理實現類來調用(後面會說明動態生成代理類的邏輯)

  2. 區別與靜態使用方式, 優先根據方法名,查找對應的選擇器;當未定義時,使用類選擇器

  3. 默認選擇器,根據name來查詢實現時,傳入的參數特殊處理下,主要是由於 spiImplMethodCacheMap 中key的生成,有一個小轉換

    若實現類上沒有 @SpiConf註解,或者 @SpiConf的註解沒有定義 name 屬性,則類的惟一標識name爲:簡單類名; 不然爲指定的name屬性
    
     若方法上顯示使用 @SpiConf 指定了name屬性,則key的生成規則爲: 方法註解上指定的name; 
     若是沒有 @SpiConf註解,或其中沒有指定name屬性,則key生成規則:  類name屬性 + 下劃線 + 方法名

    這一點單獨看可能不太好理解,所以能夠和下面的spi實現類映射關係的初始化結合起來

  4. 動態生成代理類的邏輯,放在最後進行說明

5. spi實現類映射關係表初始化

爲了不每次選擇具體的實現類時,都去加載一遍,耗時耗力好性能,所以加一個緩存是頗有必要的,這裏主要說下這個實現邏輯,以及爲啥這麼幹

緩存結構

使用了兩個Map:

  • 一個是類級別的映射關係 spiImplClassCacheMap
    • 靜態使用時,只會用搞這個
    • 動態適配時,當下面的映射關係中沒法獲取知足條件的實現時,會再次從這裏進行判斷
    • key: @SpiConf 註解中定義的name; 或者spi實現類的簡單類名
  • 一個是方法的映射關係 spiImplMethodCacheMap
    • 動態適配時, 選擇器優先從這裏進行判斷
    • key: @SpiConf 註解中定義的name; 或者是 實現類的 name + "_" + 方法名
/**
* name : spiImpl 的映射表
*/
private Map<String, SpiImplWrapper<T>> spiImplClassCacheMap;


/**
* 自適應時, 根據方法選擇實現; name : spiImpl 的映射表
*/
private Map<String, SpiImplWrapper<T>> spiImplMethodCacheMap;

實現

private void loadSpiService() {
   List<SpiImplWrapper<T>> spiServiceList = new ArrayList<>();
   List<SpiImplWrapper<T>> spiServiceMethodList = new ArrayList<>();

   ServiceLoader<T> serviceLoader = ServiceLoader.load(spiInterfaceType);

   SpiConf spiConf;
   String implName;
   int implOrder;
   for (T t : serviceLoader) {
       spiConf = t.getClass().getAnnotation(SpiConf.class);
       Map<String, String> map;
       if (spiConf == null) {
           implName = t.getClass().getSimpleName();
           implOrder = SpiImplWrapper.DEFAULT_ORDER;


           // 參數選擇器時, 要求spi實現類必須有 @SpiConf 註解, 不然選擇器沒法獲取校驗條件參數
           if (currentSelector.getSelector() instanceof ParamsSelector) {
               throw new IllegalStateException("spiImpl must contain annotation @SpiConf!");
           }

           map = Collections.emptyMap();
       } else {
           implName = spiConf.name();
           if (StringUtils.isBlank(implName)) {
               implName = t.getClass().getSimpleName();
           }

           implOrder = spiConf.order() < 0 ? SpiImplWrapper.DEFAULT_ORDER : spiConf.order();

           map = parseParms(spiConf.params());
       }

       // 添加一個類級別的封裝類
       spiServiceList.add(new SpiImplWrapper<>(t, implOrder, implName, map));


       // todo 改爲 getMethods(), 可是過濾掉 Object類中的基礎方法
       Method[] methods = t.getClass().getDeclaredMethods();
       String methodImplName;
       int methodImplOrder;
       Map<String, String> methodParams;
       for (Method method : methods) {
           spiConf = method.getAnnotation(SpiConf.class);
           if (spiConf == null) {
               continue;
           }


           // 方法上有自定義註解, 且定義的name與類實現名不一樣, 則直接採用
           // 不然採用  ServiceName_MethodName 方式定義
           if (StringUtils.isBlank(spiConf.name()) || implName.equals(spiConf.name())) {
               methodImplName = implName + "_" + method.getName();
           } else {
               methodImplName = spiConf.name();
           }


           // 優先級, 以最小的爲準 (即一個類上的優先級很低, 也能夠定義優先級高的方法)
           // 方法註解未定義順序時, 繼承類上的順序
           methodImplOrder = Math.min(implOrder, spiConf.order() < 0 ? implOrder : spiConf.order());


           // 自適應方法的參數限制, 要求繼承類上的參數
           methodParams = parseParms(spiConf.params());
           if (map.size() > 0) { // 方法的參數限定會繼承類上的參數限定
               if (methodParams.size() == 0) {
                   methodParams = map;
               } else {
                   methodParams.putAll(map);
               }
           }


           spiServiceMethodList.add(new SpiImplWrapper<>(t, methodImplOrder, methodImplName, methodParams));
       }
   }


   if (spiServiceList.size() == 0) {
       throw new IllegalStateException("no spiImpl implements spi: " + spiInterfaceType);
   }


   this.spiImplClassCacheMap = initSpiImplMap(spiServiceList);
   this.spiImplMethodCacheMap = initSpiImplMap(spiServiceMethodList);
}

private Map<String, SpiImplWrapper<T>> initSpiImplMap(List<SpiImplWrapper<T>> list) {
   // 映射爲map, 限定不能重名
   Map<String, SpiImplWrapper<T>> tempMap = new ConcurrentHashMap<>();
   for (SpiImplWrapper<T> wrapper : list) {
       if (tempMap.containsKey(wrapper.getName())) {
           throw new IllegalArgumentException("duplicate spiImpl name " + wrapper.getName());
       }

       tempMap.put(wrapper.getName(), wrapper);
   }
   return tempMap;
}

上面的邏輯能夠分爲兩塊,一塊是上半邊的初始化,獲取spiImplClassCacheMap; 下一塊則是掃描實現類的全部方法,將方法上標有@SpiConf註解的撈出來,用於初始化 spiImplMethodCacheMap

說明

  1. 緩存結構中value爲 SpiImplWrapper

    • 緩存value並非簡單的實現類,封裝類的定義以下,將條件和排序也同時封裝進去了

      private T spiImpl;
      
      private int order;
      
      
      /**
      * spiImpl 的標識name, 要求惟一
      * <p/>
      * {@link com.hust.hui.quicksilver.spi.selector.DefaultSelector 選擇具體的SpiImpl 時使用}
      */
      private String name;
      
      
      /**
      * 參數校驗規則
      * <p/>
      * {@link com.hust.hui.quicksilver.spi.selector.ParamsSelector} 選擇具體的SpiImpl 時使用
      * 要求每一個實現類都有註解  {@link SpiConf}
      */
      private Map<String, String> paramCondition;
    • name 的定義,類與方法兩個緯度的緩存中,定義規則不一樣,具體能夠看《緩存結構》這裏的說明

  2. 採用 ParamsSelector 時, 要求 @SpiConf 註解必須存在

  3. 注意掃描全部方法對應的註解, spi實現類,若是存在繼承則會出現問題

    // todo 改爲 getMethods(), 可是過濾掉 Object類中的基礎方法
     Method[] methods = t.getClass().getDeclaredMethods();

動態代碼生成

上面在談論動態使用的時候,採用的方案是,生成一個代理類,實現spi接口, 在具體的實現邏輯中,使用選擇器來獲取知足條件的實現類,而後執行相應的方法

1. 代理類格式

採用倒推方式,先給出一個實際的代理類以下,具體的實現中其實只有兩行代碼

  1. 獲取具體的實現類 (調用上面的 SpiLoader.getService(conf, methodName
  2. 執行實現類的接口
package com.hust.hui.quicksilver.spi.test.print;

import com.hust.hui.quicksilver.spi.SpiLoader;

public class IPrint$Proxy implements com.hust.hui.quicksilver.spi.test.print.IPrint {

    public void print(java.lang.String arg0) {
        try {
            com.hust.hui.quicksilver.spi.test.print.IPrint spiImpl = SpiLoader.load(com.hust.hui.quicksilver.spi.test.print.IPrint.class).getService(arg0, "print");
            spiImpl.print(arg0);
        } catch (com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e) {
            throw new java.lang.RuntimeException(e);
        }
    }

    public void adaptivePrint(java.lang.String arg0, java.lang.String arg1) {
        try {
            com.hust.hui.quicksilver.spi.test.print.IPrint spiImpl = SpiLoader.load(com.hust.hui.quicksilver.spi.test.print.IPrint.class).getService(arg0, "adaptivePrint");
            spiImpl.adaptivePrint(arg0, arg1);
        } catch (com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e) {
            throw new java.lang.RuntimeException(e);
        }
    }
}

上面給出了一個代理類的演示,那麼剩下兩個問題,一個是如何生成代理類; 一個是如何運行代理類(上面是java代碼,咱們知道運行得是字節碼才行)

代理類生成

對着上面的實現,反推代碼生成,其實比較簡單了,無非就是生成一大串的String罷了,這裏真沒什麼特殊的,貼下實現,邏輯省略

/**
     * 構建SPI接口的實現代理類, 在執行動態適配的方法時, 調用SpiLoader的 spiImpl選擇器, 選擇具體的實現類執行
     *
     * @return
     */
    public static String buildTempImpl(Class type) {
        StringBuilder codeBuilder = new StringBuilder();

        codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
        codeBuilder.append("\nimport ").append(SpiLoader.class.getName()).append(";");
        codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Proxy implements ").append(type.getCanonicalName()).append(" {\n");


        Method[] methods = type.getMethods();
        for (Method method : methods) {
            Class<?> returnType = method.getReturnType(); //函數返回值
            Class<?>[] parameterTypes = method.getParameterTypes();//函數參數列表
            Class<?>[] exceptionTypes = method.getExceptionTypes();//函數異常列表


            // build method code
            StringBuilder code = new StringBuilder(512);
            if (parameterTypes.length < 0) {   //檢查該函數參數列表中,第一個參數做爲選擇器參數
                code.append("throw new IllegalArgumentException(\"there should be one argument for selector to choose spiImpl\")");
            } else { // 沒有 SpiAdaptive註解的, 採用默認的註解方式
                code.append("try{\n");
                code.append(type.getName()).append(" spiImpl=")
                        .append("SpiLoader.load(")
                        .append(type.getName()).append(".class")
                        .append(").getService(arg0,\"")
                        .append(method.getName())
                        .append("\");");

                if (!"void".equals(returnType.getName())) {
                    code.append("return ");
                }
                code.append("spiImpl.").append(method.getName()).append("(arg0");
                for (int i = 1; i < parameterTypes.length; i++) {
                    code.append(",").append("arg").append(i);
                }
                code.append(");");
                code.append("\n} catch(com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e){\nthrow new java.lang.RuntimeException(e);\n}");
            }


            // build method signature
            codeBuilder.append("\npublic ").append(returnType.getName()).append(" ").append(method.getName())
                    .append("(").append(parameterTypes[0].getName()).append(" arg0");

            for (int i = 1; i < parameterTypes.length; i++) {
                codeBuilder.append(", ").append(parameterTypes[i].getName()).append(" arg").append(i);
            }
            codeBuilder.append(") ");
            if (exceptionTypes.length > 0) {
                codeBuilder.append("throw ").append(exceptionTypes[0].getName());
                for (int i = 1; i < exceptionTypes.length; i++) {
                    codeBuilder.append(", ").append(exceptionTypes[i].getName());
                }
            }
            codeBuilder.append("{\n");
            codeBuilder.append(code.toString()).append("\n}");
        }

        codeBuilder.append("\n}");
        return codeBuilder.toString();
    }

動態編譯運行

動態編譯,最開始想的是利用jdk的動態編譯方式,試來試去沒搞成功,而後選擇了一個折中的方案,把代理類當作是groovy代碼,利用 GroovyEngine 來實現動態運行, 這一塊的邏輯也超級簡單,下面的短短几行代碼便可; 後面有空單獨研究下java的動態編譯

@SuppressWarnings("unchecked")
public static <T> T compile(String code, Class<T> interfaceType, ClassLoader classLoader) throws SpiProxyCompileException {
   GroovyClassLoader loader = new GroovyClassLoader(classLoader);
   Class clz = loader.parseClass(code);

   if (!interfaceType.isAssignableFrom(clz)) {
       throw new IllegalStateException("illegal proxy type!");
   }


   try {
       return (T) clz.newInstance();
   } catch (Exception e) {
       throw new SpiProxyCompileException("init spiProxy error! msg: " + e.getMessage());
   }
}

小結

至此,核心的東西基本上都過了一遍,主要的設計思路,實現邏輯,執行流程都說完了,目前的實現中,仍有一些缺陷,歡迎提出問題和建議,無比感激

其餘

博客系列連接:

源碼地址:

https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-spi

相關文章
相關標籤/搜索