SPI框架實(shí)現(xiàn)之旅三:實(shí)現(xiàn)說明

SPI框架實(shí)現(xiàn)之旅三:實(shí)現(xiàn)說明

前一篇 《SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)》中浩考,介紹了幾個(gè)定義的接口婶肩,注解;敘述了實(shí)現(xiàn)流程说搅;并簡(jiǎn)單的介紹了 SpiLoader中的部分實(shí)現(xiàn)炸枣; 本篇?jiǎng)t主要介紹SpiLoader類的實(shí)現(xiàn)

類圖結(jié)構(gòu)如下:

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

SpiLoader 全解析

spiImpl選擇的核心類,包括初始化選擇器,初始化spiImpl實(shí)現(xiàn)列表适肠,解析spiImpl的選擇條件霍衫,返回具體的實(shí)現(xiàn)類等

1. 獲取spiLoader對(duì)象

SpiLoader 是一個(gè)泛型對(duì)象,每個(gè)SPI接口侯养,對(duì)應(yīng)一個(gè)SpiLoader<T> 對(duì)象敦跌,我們提供了一個(gè)靜態(tài)方法來(lái)獲取這個(gè)對(duì)象

實(shí)現(xiàn)

優(yōu)先從緩存中獲取, 如果緩存沒有逛揩,則新建一個(gè)柠傍;緩存中有, 則直接返回

/**
* spiLoader緩存, 其中key為 spi接口, value為對(duì)應(yīng)的Loader對(duì)象
*/
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;
}

說明

  • 上面有幾個(gè)校驗(yàn)辩稽,前一篇已經(jīng)說明惧笛,不再贅述
  • 上面新建對(duì)象,不是線程安全的

2. 新建 SpiLoader對(duì)象

創(chuàng)建對(duì)象逞泄,主要會(huì)初始化選擇器

實(shí)現(xiàn)

private SpiLoader(Class<T> type) {
   // 初始化默認(rèn)的選擇器, 為保留項(xiàng)目, 必然會(huì)提供的服務(wù)
   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) {
   // 優(yōu)先從選擇器緩存中獲取類型對(duì)應(yīng)的選擇器
   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);
   }
}

說明

  • 持有一個(gè)選擇器緩存列表患整,selectorInstanceCacheMap
    • 保證每種類型的選擇器,在這個(gè)SpiLoader中炭懊,只會(huì)有一個(gè)實(shí)例存在
    • 不做成全局唯一的原因是盡量隔離, 比如 ParamsSelector 內(nèi)部緩存了spi實(shí)現(xiàn)的列表并级,如果全局公用的話,就會(huì)混掉侮腹,導(dǎo)致這個(gè)列表中就出現(xiàn)非這個(gè)spi接口的實(shí)現(xiàn)類
  • 類選擇器 + 方法選擇器
    • currentSelector : 類選擇器, 解析 @Spi 注解獲取嘲碧,適用于靜態(tài)選擇 + 動(dòng)態(tài)選擇兩種使用方式
    • currentMethodSelector : 方法選擇器,解析 @SpiAdaptive 注解獲取父阻, 僅適用于動(dòng)態(tài)選擇SPI實(shí)現(xiàn)的方式
    • 優(yōu)先級(jí): 方法上定義的選擇器 由于 類上定義的選擇器愈涩; 方法上未定義時(shí),默認(rèn)使用類定義的選擇器

3. 靜態(tài)使用

靜態(tài)使用方式加矛,表示根據(jù)傳入的條件履婉,選擇一個(gè)滿足條件的實(shí)現(xiàn)返回

實(shí)現(xiàn)

/**
* 根據(jù)傳入條件, 選擇具體的spi實(shí)現(xiàn)類
* <p/>
* 這里要求conf的類型和選擇器的參數(shù)類型匹配, 否則會(huì)嘗試使用默認(rèn)的選擇器補(bǔ)救, 若補(bǔ)救失敗, 則拋異常
*
* @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())) {

       /**
        * 參數(shù)類型不匹配時(shí), 判斷是否可以根據(jù)默認(rèn)的選擇器來(lái)獲取
        */
       if (conf instanceof String) {
           return (T) DEFAULT_SELECTOR.getSelector().selector(spiImplClassCacheMap, conf);
       }


       /**
        * 參數(shù)類型完全不匹配, 則拋參數(shù)異常
        */
       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實(shí)現(xiàn)的緩存映射表,優(yōu)先判斷緩存映射表是否存在斟览,不存在時(shí)需要初始化毁腿;存在時(shí),則進(jìn)入校驗(yàn)邏輯

  2. 校驗(yàn)

    校驗(yàn)傳入的參數(shù)苛茂,是否匹配當(dāng)前的選擇器參數(shù)類型已烤,為了保證選擇器可以正常運(yùn)行

    當(dāng)不匹配時(shí),會(huì)有一個(gè)兼容邏輯妓羊,判斷傳參類型是否為String胯究, 是則采用默認(rèn)的選擇器,根據(jù)name來(lái)選擇spi實(shí)現(xiàn) (這種實(shí)現(xiàn)可能造成選擇的實(shí)現(xiàn)不是預(yù)期的)

  3. 靜態(tài)使用方式躁绸,使用類定義選擇器 : currentSelector

    • 靜態(tài)使用的方式裕循,目標(biāo)就是事前就確認(rèn)使用這個(gè)實(shí)現(xiàn)了臣嚣,不會(huì)出現(xiàn)變動(dòng)了; 相當(dāng)于一次確認(rèn)剥哑,所有的調(diào)用都是確認(rèn)的

    • 靜態(tài)使用硅则,方法注解的選擇器無(wú)效。這個(gè)我們從逆向的思路進(jìn)行解釋

        IPrint 是一個(gè)Spi接口株婴, 有兩個(gè)實(shí)現(xiàn)   FilePrint,   ConsolePrint
        假設(shè) `currentSelector=DefaultSelector`抢埋, 方法  methodA 上定義的是  ParamsSelector 時(shí)
        
        靜態(tài)使用方式,獲取一個(gè)spi實(shí)現(xiàn)督暂,希望在所有的spi接口使用處揪垄,都輸出到文件,用戶根據(jù) `FilePrint` 選擇  FilePrint 這個(gè)類來(lái)執(zhí)行具體的輸出邏輯逻翁, 如果在調(diào)用 methodA 方法執(zhí)行時(shí)饥努, 假設(shè)根據(jù)  ParamsSelector 判斷, ConsolePrint 才滿足這兒條件八回,這是相當(dāng)于在具體實(shí)現(xiàn)時(shí)酷愧,換成了另一個(gè) ConsolePrint, 這下子就與我們的初衷背離了(如果目標(biāo)是想實(shí)現(xiàn)這個(gè)場(chǎng)景,顯然動(dòng)態(tài)適配的方式才是正確的使用姿勢(shì))
      
  4. loadService 的邏輯后面詳細(xì)說明

4. 動(dòng)態(tài)使用

動(dòng)態(tài)使用區(qū)別于靜態(tài)的直接確定實(shí)現(xiàn)類缠诅, 通過getService 獲取的并不是某個(gè)特定對(duì)的實(shí)現(xiàn)類溶浴,而是一個(gè)動(dòng)態(tài)生成的代理,每次具體執(zhí)行之前管引,會(huì)去判斷一下士败,應(yīng)該選擇哪一個(gè)實(shí)現(xiàn)來(lái)執(zhí)行

設(shè)計(jì)的出發(fā)點(diǎn)

可以考慮下,我們的目標(biāo)是在執(zhí)行方法之前褥伴,需要判斷一下哪個(gè)實(shí)現(xiàn)類滿足要求谅将,選擇這個(gè)實(shí)現(xiàn)類來(lái)執(zhí)行這個(gè)方法,那么我們可以怎么去做重慢?

考慮到切面的方式饥臂,如果有一種手段,在方法執(zhí)行之前似踱,織入一段業(yè)務(wù)邏輯隅熙,就可以達(dá)到上面的目的

最開始雖然是怎么想的,但是有點(diǎn)尷尬的是核芽,不知道怎么去實(shí)現(xiàn)囚戚;因此換了一個(gè)思路,我自己新生成一個(gè)接口的實(shí)現(xiàn)類狞洋,在這個(gè)實(shí)現(xiàn)類里面做選擇邏輯弯淘,然后把這個(gè)實(shí)現(xiàn)類對(duì)象返回

實(shí)現(xiàn)如下

和靜態(tài)實(shí)現(xiàn)的邏輯差不多绿店,一般流程如下:

  • 判斷spi實(shí)現(xiàn)類的映射關(guān)系表是否初始化吉懊,若沒有則初始化
  • 獲取選擇器
    • 優(yōu)先從方法選擇器中查找庐橙, 若存在,則直接選中借嗽;
    • 不存在态鳖,則使用類選擇器
  • 校驗(yàn):判斷傳入條件參數(shù)類型是否滿足選擇器的參數(shù)類型匹配(將方法的第一個(gè)參數(shù),作為選擇器的選擇條件)
  • 返回實(shí)現(xiàn)類
@SuppressWarnings("unchecked")
public T getService(Object conf, String methodName) throws NoSpiMatchException {
   if (spiImplClassCacheMap == null || spiImplClassCacheMap.size() == 0) {
       loadSpiService();
   }


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

   if (!selector.getConditionType().isAssignableFrom(conf.getClass())) { // 選擇器類型校驗(yàn)
       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 {
       // 采用默認(rèn)的選擇器,根據(jù)指定name 進(jìn)行查詢時(shí), 需要兼容一下, 因?yàn)閙ethod對(duì)應(yīng)的緩存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. 這個(gè)方法通常是由框架生成的代理實(shí)現(xiàn)類來(lái)調(diào)用(后面會(huì)說明動(dòng)態(tài)生成代理類的邏輯)

  2. 區(qū)別與靜態(tài)使用方式恶导, 優(yōu)先根據(jù)方法名浆竭,查找對(duì)應(yīng)的選擇器;當(dāng)未定義時(shí)惨寿,使用類選擇器

  3. 默認(rèn)選擇器邦泄,根據(jù)name來(lái)查詢實(shí)現(xiàn)時(shí),傳入的參數(shù)特殊處理下裂垦,主要是因?yàn)?spiImplMethodCacheMap 中key的生成顺囊,有一個(gè)小轉(zhuǎn)換

     若實(shí)現(xiàn)類上沒有 @SpiConf注解,或者 @SpiConf的注解沒有定義 name 屬性蕉拢,則類的唯一標(biāo)識(shí)name為:簡(jiǎn)單類名特碳; 否則為指定的name屬性
     
     若方法上顯示使用 @SpiConf 指定了name屬性,則key的生成規(guī)則為: 方法注解上指定的name晕换; 
     如果沒有 @SpiConf注解午乓,或其中沒有指定name屬性,則key生成規(guī)則:  類name屬性 + 下劃線 + 方法名
    

    這一點(diǎn)單獨(dú)看可能不太好理解闸准,因此可以和下面的spi實(shí)現(xiàn)類映射關(guān)系的初始化結(jié)合起來(lái)

  4. 動(dòng)態(tài)生成代理類的邏輯益愈,放在最后進(jìn)行說明

5. spi實(shí)現(xiàn)類映射關(guān)系表初始化

為了避免每次選擇具體的實(shí)現(xiàn)類時(shí),都去加載一遍夷家,耗時(shí)耗力好性能腕唧,因此加一個(gè)緩存是很有必要的,這里主要說下這個(gè)實(shí)現(xiàn)邏輯瘾英,以及為啥這么干

緩存結(jié)構(gòu)

使用了兩個(gè)Map:

  • 一個(gè)是類級(jí)別的映射關(guān)系 spiImplClassCacheMap
    • 靜態(tài)使用時(shí)枣接,只會(huì)用搞這個(gè)
    • 動(dòng)態(tài)適配時(shí),當(dāng)下面的映射關(guān)系中無(wú)法獲取滿足條件的實(shí)現(xiàn)時(shí)缺谴,會(huì)再次從這里進(jìn)行判斷
    • key: @SpiConf 注解中定義的name但惶; 或者spi實(shí)現(xiàn)類的簡(jiǎn)單類名
  • 一個(gè)是方法的映射關(guān)系 spiImplMethodCacheMap
    • 動(dòng)態(tài)適配時(shí), 選擇器優(yōu)先從這里進(jìn)行判斷
    • key: @SpiConf 注解中定義的name湿蛔; 或者是 實(shí)現(xiàn)類的 name + "_" + 方法名
/**
* name : spiImpl 的映射表
*/
private Map<String, SpiImplWrapper<T>> spiImplClassCacheMap;


/**
* 自適應(yīng)時(shí), 根據(jù)方法選擇實(shí)現(xiàn); name : spiImpl 的映射表
*/
private Map<String, SpiImplWrapper<T>> spiImplMethodCacheMap;

實(shí)現(xiàn)

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;


           // 參數(shù)選擇器時(shí), 要求spi實(shí)現(xiàn)類必須有 @SpiConf 注解, 否則選擇器無(wú)法獲取校驗(yàn)條件參數(shù)
           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());
       }

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


       // todo 改成 getMethods(), 但是過濾掉 Object類中的基礎(chǔ)方法
       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與類實(shí)現(xiàn)名不同, 則直接采用
           // 否則采用  ServiceName_MethodName 方式定義
           if (StringUtils.isBlank(spiConf.name()) || implName.equals(spiConf.name())) {
               methodImplName = implName + "_" + method.getName();
           } else {
               methodImplName = spiConf.name();
           }


           // 優(yōu)先級(jí), 以最小的為準(zhǔn) (即一個(gè)類上的優(yōu)先級(jí)很低, 也可以定義優(yōu)先級(jí)高的方法)
           // 方法注解未定義順序時(shí), 繼承類上的順序
           methodImplOrder = Math.min(implOrder, spiConf.order() < 0 ? implOrder : spiConf.order());


           // 自適應(yīng)方法的參數(shù)限制, 要求繼承類上的參數(shù)
           methodParams = parseParms(spiConf.params());
           if (map.size() > 0) { // 方法的參數(shù)限定會(huì)繼承類上的參數(shù)限定
               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;
下一塊則是掃描實(shí)現(xiàn)類的所有方法阳啥,將方法上標(biāo)有@SpiConf注解的撈出來(lái)添谊,用于初始化 spiImplMethodCacheMap

說明

  1. 緩存結(jié)構(gòu)中value為 SpiImplWrapper

    • 緩存value并不是簡(jiǎn)單的實(shí)現(xiàn)類,封裝類的定義如下察迟,將條件和排序也同時(shí)封裝進(jìn)去了

      private T spiImpl;
      
      private int order;
      
      
      /**
      * spiImpl 的標(biāo)識(shí)name, 要求唯一
      * <p/>
      * {@link com.hust.hui.quicksilver.spi.selector.DefaultSelector 選擇具體的SpiImpl 時(shí)使用}
      */
      private String name;
      
      
      /**
      * 參數(shù)校驗(yàn)規(guī)則
      * <p/>
      * {@link com.hust.hui.quicksilver.spi.selector.ParamsSelector} 選擇具體的SpiImpl 時(shí)使用
      * 要求每個(gè)實(shí)現(xiàn)類都有注解  {@link SpiConf}
      */
      private Map<String, String> paramCondition;
      
    • name 的定義斩狱,類與方法兩個(gè)緯度的緩存中耳高,定義規(guī)則不同,具體可以看《緩存結(jié)構(gòu)》這里的說明

  2. 采用 ParamsSelector 時(shí)所踊, 要求 @SpiConf 注解必須存在

  3. 注意掃描所有方法對(duì)應(yīng)的注解, spi實(shí)現(xiàn)類泌枪,如果存在繼承則會(huì)出現(xiàn)問題

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

動(dòng)態(tài)代碼生成

上面在談?wù)搫?dòng)態(tài)使用的時(shí)候,采用的方案是秕岛,生成一個(gè)代理類碌燕,實(shí)現(xiàn)spi接口, 在具體的實(shí)現(xiàn)邏輯中继薛,使用選擇器來(lái)獲取滿足條件的實(shí)現(xiàn)類修壕,然后執(zhí)行相應(yīng)的方法

1. 代理類格式

采用倒推方式,先給出一個(gè)實(shí)際的代理類如下遏考,具體的實(shí)現(xiàn)中其實(shí)只有兩行代碼

  1. 獲取具體的實(shí)現(xiàn)類 (調(diào)用上面的 SpiLoader.getService(conf, methodName
  2. 執(zhí)行實(shí)現(xiàn)類的接口
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);
        }
    }
}

上面給出了一個(gè)代理類的演示叠殷,那么剩下兩個(gè)問題,一個(gè)是如何生成代理類诈皿; 一個(gè)是如何運(yùn)行代理類(上面是java代碼林束,我們知道運(yùn)行得是字節(jié)碼才行)

代理類生成

對(duì)著上面的實(shí)現(xiàn),反推代碼生成稽亏,其實(shí)比較簡(jiǎn)單了壶冒,無(wú)非就是生成一大串的String罷了,這里真沒什么特殊的截歉,貼下實(shí)現(xiàn)胖腾,邏輯省略

    /**
     * 構(gòu)建SPI接口的實(shí)現(xiàn)代理類, 在執(zhí)行動(dòng)態(tài)適配的方法時(shí), 調(diào)用SpiLoader的 spiImpl選擇器, 選擇具體的實(shí)現(xiàn)類執(zhí)行
     *
     * @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(); //函數(shù)返回值
            Class<?>[] parameterTypes = method.getParameterTypes();//函數(shù)參數(shù)列表
            Class<?>[] exceptionTypes = method.getExceptionTypes();//函數(shù)異常列表


            // build method code
            StringBuilder code = new StringBuilder(512);
            if (parameterTypes.length < 0) {   //檢查該函數(shù)參數(shù)列表中,第一個(gè)參數(shù)作為選擇器參數(shù)
                code.append("throw new IllegalArgumentException(\"there should be one argument for selector to choose spiImpl\")");
            } else { // 沒有 SpiAdaptive注解的, 采用默認(rèn)的注解方式
                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();
    }

動(dòng)態(tài)編譯運(yùn)行

動(dòng)態(tài)編譯瘪松,最開始想的是利用jdk的動(dòng)態(tài)編譯方式咸作,試來(lái)試去沒搞成功,然后選擇了一個(gè)折中的方案宵睦,把代理類看成是groovy代碼记罚,利用 GroovyEngine 來(lái)實(shí)現(xiàn)動(dòng)態(tài)運(yùn)行, 這一塊的邏輯也超級(jí)簡(jiǎn)單,下面的短短幾行代碼即可壳嚎; 后面有空單獨(dú)研究下java的動(dòng)態(tài)編譯

@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());
   }
}

4. 其他

博客系列鏈接:

-SPI框架實(shí)現(xiàn)之旅一:背景介紹
-SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)
-SPI框架實(shí)現(xiàn)之旅三:實(shí)現(xiàn)說明
-SPI框架實(shí)現(xiàn)之旅四:使用測(cè)試

源碼地址:
https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-spi

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桐智,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烟馅,更是在濱河造成了極大的恐慌说庭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑趁,死亡現(xiàn)場(chǎng)離奇詭異刊驴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門捆憎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舅柜,“玉大人,你說我怎么就攤上這事攻礼。” “怎么了栗柒?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵礁扮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瞬沦,道長(zhǎng)太伊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任逛钻,我火速辦了婚禮僚焦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘曙痘。我一直安慰自己芳悲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布边坤。 她就那樣靜靜地躺著名扛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茧痒。 梳的紋絲不亂的頭發(fā)上肮韧,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音旺订,去河邊找鬼弄企。 笑死,一個(gè)胖子當(dāng)著我的面吹牛区拳,可吹牛的內(nèi)容都是我干的拘领。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼樱调,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼院究!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起本涕,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤业汰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后菩颖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體样漆,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年晦闰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了放祟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鳍怨。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖跪妥,靈堂內(nèi)的尸體忽然破棺而出鞋喇,到底是詐尸還是另有隱情,我是刑警寧澤眉撵,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布侦香,位于F島的核電站,受9級(jí)特大地震影響纽疟,放射性物質(zhì)發(fā)生泄漏罐韩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一污朽、第九天 我趴在偏房一處隱蔽的房頂上張望散吵。 院中可真熱鬧,春花似錦蟆肆、人聲如沸矾睦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顷锰。三九已至,卻和暖如春亡问,著一層夾襖步出監(jiān)牢的瞬間官紫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工州藕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留束世,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓床玻,卻偏偏與公主長(zhǎng)得像毁涉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锈死,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容