SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)

SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)

上一篇簡(jiǎn)單的說(shuō)了一下spi相關(guān)的東西星压, 接下來(lái)我們準(zhǔn)備開(kāi)動(dòng),本篇博文主要集中在一些術(shù)語(yǔ)喂窟,使用規(guī)范的約定和使用方式

設(shè)計(jì)思路

下圖圍繞 SpiLoader 為中心熟史,描述了三個(gè)主要的流程:

  1. load所有的spi實(shí)現(xiàn)
  2. 初始化選擇器 selector
  3. 獲取spi實(shí)現(xiàn)類 (or一個(gè)實(shí)現(xiàn)類代理)
https://static.oschina.net/uploads/img/201705/26185143_ULnL.png

基礎(chǔ)類說(shuō)明

主要介紹一下框架中涉及到的接口和注解,并指出需要注意的點(diǎn)

1. Selector 選擇器

為了最大程度的支持業(yè)務(wù)方對(duì)spi實(shí)現(xiàn)類的選擇春畔,我們定義了一個(gè)選擇器的概念,用于獲取spi實(shí)現(xiàn)類

接口定義如下:

public interface ISelector<T> {
    <K> K selector(Map<String, SpiImplWrapper<K>> map, T conf) throws NoSpiMatchException;
}

結(jié)合上面的接口定義岛都,我們可以考慮下律姨,選擇器應(yīng)該如何工作?

  • 根據(jù)傳入的條件臼疫,從所有的實(shí)現(xiàn)類中择份,找到一個(gè)最匹配的實(shí)現(xiàn)類返回
  • 如果查不到,則拋一個(gè)異常NoSpiMatchException出去

所以傳入的參數(shù)會(huì)是兩個(gè)多矮, 一個(gè)是所有的實(shí)現(xiàn)類列表map(至于上面為什么用map,后續(xù)分析)哈打,一個(gè)是用于判斷的輸入條件conf

框架中會(huì)提供兩種基本的選擇器實(shí)現(xiàn)塔逃,

  • DefaultSelector , 對(duì)每個(gè)實(shí)現(xiàn)類賦予唯一的name料仗,默認(rèn)選擇器則表示根據(jù)name來(lái)查找實(shí)現(xiàn)類
  • ParamsSelector湾盗, 在實(shí)現(xiàn)類上加上 @SpiConf 注解,定義其中的 params立轧,當(dāng)傳入的參數(shù)(conf)格粪, 能完全匹配定義的params,表示這個(gè)實(shí)現(xiàn)類就是你所需要的

自定義實(shí)現(xiàn)

自定義實(shí)現(xiàn)比較簡(jiǎn)單氛改,實(shí)現(xiàn)上面的接口即可

2. Spi 注解

要求所有的spi接口帐萎,都必須有這個(gè)注解;

定義如下

主要是有一個(gè)參數(shù)胜卤,用于指定是選擇器類型疆导,定義spi接口的默認(rèn)選擇器,

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Spi {
    Class<? extends ISelector> selector() default DefaultSelector.class;
}

說(shuō)明

在上一篇《SPI框架實(shí)現(xiàn)之旅一》中葛躏,使用jdk的spi方式中澈段,并沒(méi)有使用注解依然可以正常工作,我們這里定義這個(gè)注解且要求必需有舰攒,出于下面幾個(gè)考慮

  • 醒目败富,告訴開(kāi)發(fā)者,這個(gè)接口是聲明的spi接口摩窃, 使用的時(shí)候注意下
  • 加入選擇器參數(shù)兽叮,方便用戶擴(kuò)展自己的選擇方式

3. SpiAdaptive 注解

對(duì)需要自適應(yīng)的場(chǎng)景,為了滿足一個(gè)spi接口,應(yīng)用多重不同的選擇器場(chǎng)景充择,可以加上這個(gè)注解德玫;
如果不加這個(gè)注解,則表示采用默認(rèn)的選擇器來(lái)自適應(yīng)

接口說(shuō)明

/**
 * SPI 自適應(yīng)注解, 表示該方法會(huì)用到spi實(shí)現(xiàn)
 * <p/>
 * Created by yihui on 2017/5/24.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SpiAdaptive {
    Class<? extends ISelector> selector() default DefaultSelector.class;
}

說(shuō)明

這個(gè)注解內(nèi)容和 @Spi 基本上一模一樣椎麦,唯一的區(qū)別是一個(gè)放在類上宰僧,一個(gè)放在方法上,那么為什么這么考慮观挎?

  • @Spi 注解放在類上琴儿,更多的表名這個(gè)接口是我們定義的一個(gè)SPI接口,但是使用方式可以有兩種(靜態(tài) + 動(dòng)態(tài)確認(rèn))
  • @SpiAdaptive 只能在自適應(yīng)的場(chǎng)景下使用嘁捷,用于額外指定spi接口中某個(gè)方法的選擇器 (如果一個(gè)spi接口全部只需要一個(gè)選擇器即可造成,那么可以不使用這個(gè)注解)

如下面的這個(gè)例子,print方法和 echo方法其實(shí)是等價(jià)的雄嚣,都是采用 DefaultSelector 來(lái)確認(rèn)具體的實(shí)現(xiàn)類晒屎;而 writepp 方法則是采用 ParamsSelector 選擇器;

/**
 * Created by yihui on 2017/5/25.
 */
@Spi
public interface ICode {

    void print(String name, String contet);


    @SpiAdaptive
    void echo(String name, String content);


    @SpiAdaptive(selector = ParamsSelector.class)
    void write(Context context, String content);


    @SpiAdaptive(selector = ParamsSelector.class)
    void pp(Context context, String content);
}

4. SpiConf 注解

這個(gè)主鍵主要是用在實(shí)現(xiàn)類上(或?qū)崿F(xiàn)類的方法上),里面存儲(chǔ)一些選擇條件缓升,通常是和Selector搭配使用

定義如下

定義了三個(gè)字段:

  • name 唯一標(biāo)識(shí)鼓鲁,用于 DefaultSelector
  • params 參數(shù)條件港谊, 用于 ParamsSelector骇吭;
  • order : 優(yōu)先級(jí), 主要是為了解決多個(gè)實(shí)現(xiàn)類都滿足選擇條件時(shí)歧寺, 應(yīng)該選擇哪一個(gè) (談到這里就有個(gè)想法燥狰, 通過(guò)一個(gè)參數(shù),來(lái)選擇是否讓滿足條件的全部返回)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SpiConf {

    /**
     * 唯一標(biāo)識(shí)
     *
     * @return
     */
    String name() default "";


    /**
     * 參數(shù)過(guò)濾, 單獨(dú)一個(gè)元素,表示參數(shù)必須包含; 用英文分號(hào),左邊為參數(shù)名,右邊為參數(shù)值,表示參數(shù)的值必須是右邊的
     * <p/>
     * 形如  {"a", "a:12", "b:TAG"}
     *
     * @return
     */
    String[] params() default {};


    /**
     * 排序, 越小優(yōu)先級(jí)越高
     *
     * @return
     */
    int order() default -1;
}

說(shuō)明

SpiConf 注解可以修飾類斜筐,也可以修飾方法龙致,因此當(dāng)一個(gè)實(shí)現(xiàn)類中,類和方法都有這個(gè)注解時(shí)顷链, 怎么處理 净当?

以下面的這個(gè)測(cè)試類進(jìn)行說(shuō)明

/**
 * Created by yihui on 2017/5/25.
 */
@SpiConf(params = "code", order = 1)
public class ConsoleCode implements ICode {
    @Override
    public void print(String name, String contet) {
        System.out.println("console print:--->" + contet);
    }


    /**
     * 顯示指定了name, 因此可以直接通過(guò) consoleEcho 來(lái)確定調(diào)用本實(shí)現(xiàn)方法
     * @param name
     * @param content
     */
    @Override
    @SpiConf(name = "consoleEcho")
    public void echo(String name, String content) {
        System.out.println("console echo:---->" + content);
    }


    /**
     * 實(shí)際的優(yōu)先級(jí)取 方法 和類上的最高優(yōu)先級(jí), 實(shí)際為1; 
     * `ParamsSelector`選擇器時(shí)蕴潦, 執(zhí)行該方法的條件等同于  `{"code", "type:console"}`
     * @param context
     * @param content
     */
    @Override
    @SpiConf(params = {"type:console"}, order = 3)
    public void write(Context context, String content) {
        System.out.println("console write:---->" + content);
    }
}

在設(shè)計(jì)中像啼,遵循下面幾個(gè)原則:

  • 類上的SpiConf注解, 默認(rèn)適用與類中的所有方法
  • 方法上有SpiConf注解潭苞,采取下面的規(guī)則
    • 方法注解聲明name時(shí)忽冻,兩個(gè)會(huì)同時(shí)生效,即想調(diào)用上面的echo方法此疹, 通過(guò)傳入 ConsoleCode(類注解不顯示賦值時(shí)僧诚,采用類名代替) 和 consoleEcho 等價(jià)
    • 方法注解未聲明name時(shí)遮婶,只能通過(guò)類注解上定義的name(or默認(rèn)的類名)來(lái)選擇
    • order,取最高優(yōu)先級(jí)湖笨,如上面的 write 方法的優(yōu)先級(jí)是 1; 當(dāng)未顯示定義order時(shí)旗扑,以定義的為準(zhǔn)
    • params: 取并集,即要求類上 + 方法上的條件都滿足

SPI加載器

spi加載器的主要業(yè)務(wù)邏輯集中在 SpiLoader 類中慈省,包含通過(guò)spi接口臀防,獲取所有的實(shí)現(xiàn)類; 獲取spi接口對(duì)應(yīng)的選擇器 (包括類對(duì)應(yīng)的選擇器边败, 方法對(duì)應(yīng)的選擇器)袱衷; 返回Spi接口實(shí)現(xiàn)類(靜態(tài)確認(rèn)的實(shí)現(xiàn)類,自適應(yīng)的代理類)

從上面的簡(jiǎn)述笑窜,基本上可以看出這個(gè)類劃分為三個(gè)功能點(diǎn)致燥, 下面將逐一說(shuō)明,本篇博文主要集中在邏輯的設(shè)計(jì)層排截,至于優(yōu)化(如懶加載嫌蚤,緩存優(yōu)化等) 放置下一篇博文單獨(dú)敘述

1. 加載spi實(shí)現(xiàn)類

這一塊比較簡(jiǎn)單,我們直接利用了jdk的 ServiceLoader 來(lái)根據(jù)接口断傲,獲取所有的實(shí)現(xiàn)類脱吱;因此我們的spi實(shí)現(xiàn),需要滿足jdk定義的這一套規(guī)范

具體的代碼業(yè)務(wù)邏輯非常簡(jiǎn)單艳悔,大致流程如下

 if (null == spiInterfaceType) {
  throw new IllegalArgumentException("common cannot be null...");
}

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


if (!withSpiAnnotation(spiInterfaceType)) {
  throw new IllegalArgumentException("common class:" + spiInterfaceType + " must have the annotation of @Spi");
}
   
ServiceLoader<T> serviceLoader = ServiceLoader.load(spiInterfaceType);
for(T spiImpl: serviceLoader) {
    // xxx
}

注意

  • 因?yàn)槭褂昧薺dk的標(biāo)準(zhǔn)急凰,因此每定義一個(gè)spi接口女仰,必須在 META_INF.services 下新建一個(gè)文件猜年, 文件名為包含包路徑的spi接口名, 內(nèi)部為包含包路徑的實(shí)現(xiàn)類名
  • 每個(gè)spi接口疾忍,要求必須有 @Spi 注解
  • Spi接口必須是 interface 類型乔外, 不支持抽象類和類的方式

拓展

雖然這里直接使用了spi的規(guī)范,我們其實(shí)完全可以自己定義標(biāo)準(zhǔn)的一罩,只要能將這個(gè)接口的所有實(shí)現(xiàn)類找到杨幼, 怎么實(shí)現(xiàn)都可以由你定義

如使用spring框架后,可以考慮通過(guò) applicationContext.getBeansOfAnnotaion(xxx ) 來(lái)獲取所有的特定注解的bean聂渊,這樣就可以不需要自己新建一個(gè)文件差购,來(lái)存儲(chǔ)spi接口和其實(shí)現(xiàn)類的映射關(guān)系了

構(gòu)建spi實(shí)現(xiàn)的關(guān)系表

上面獲取了spi實(shí)現(xiàn)類,顯然我們的目標(biāo)并不局限于簡(jiǎn)單的獲取實(shí)現(xiàn)類汉嗽,在獲取實(shí)現(xiàn)類之后欲逃,還需要解析其中的 @SpiConf 注解信息,用于表示要選擇這個(gè)實(shí)現(xiàn)饼暑,必須滿足什么樣的條件

SpiImplWrapper : spi實(shí)現(xiàn)類稳析,以及定義的各種條件的封裝類

注解的解析過(guò)程流程如下:

  • name: 注解定義時(shí)洗做,采用定義的值; 否則采用簡(jiǎn)單類名 (因此一個(gè)系統(tǒng)中不允許兩個(gè)實(shí)現(xiàn)類同名的情況)
  • order: 優(yōu)先級(jí)彰居, 注解定義時(shí)诚纸,采用定義的值;未定義時(shí)采用默認(rèn)陈惰;
  • params: 參數(shù)約束條件畦徘, 會(huì)取類上和方法上的并集(原則上要求類上的約束和方法上的約束不能沖突)
List<SpiImplWrapper<T>> spiServiceList = new ArrayList<>();

// 解析注解
spiConf = t.getClass().getAnnotation(SpiConf.class);
  Map<String, String> map;
  if (spiConf == null) { // 沒(méi)有添加注解時(shí), 采用默認(rèn)的方案
      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));
  
  
  // ------------
  // 解析參數(shù)的方法
  
   private Map<String, String> parseParms(String[] params) {
        if (params.length == 0) {
            return Collections.emptyMap();
        }


        Map<String, String> map = new HashMap<>(params.length);
        String[] strs;
        for (String param : params) {
            strs = StringUtils.split(param, ":");

            if (strs.length >= 2) {
                map.put(strs[0].trim(), strs[1].trim());
            } else if (strs.length == 1) {
                map.put(strs[0].trim(), null);
            }
        }
        return map;
    }

2. 初始化選擇器

我們的選擇器會(huì)區(qū)分為兩類奴潘,一個(gè)是類上定義的選擇器旧烧, 一個(gè)是方法上定義的選擇器; 在自適應(yīng)的使用方式中画髓,方法上定義的優(yōu)先級(jí) > 類上定義

簡(jiǎn)單來(lái)講掘剪,初始化選擇器,就是掃一遍SPI接口中的注解奈虾,實(shí)例化選擇器后夺谁,緩存住對(duì)應(yīng)的結(jié)果, 實(shí)現(xiàn)如下

 /**
* 選擇器, 根據(jù)條件, 選擇具體的 SpiImpl;
*/
private SelectorWrapper currentSelector;


/**
* 自適應(yīng)時(shí), 方法對(duì)應(yīng)的選擇器
*/
private Map<String, SelectorWrapper> currentMethodSelector;


/**
* 每一個(gè) SpiLoader 中, 每種類型的選擇器, 只保存一個(gè)實(shí)例
* 因此可以在選擇器中, 如{@link ParamsSelector} 對(duì)spiImplMap進(jìn)行處理并緩存結(jié)果
*/
private ConcurrentHashMap<Class, SelectorWrapper> selectorInstanceCacheMap = new ConcurrentHashMap<>();
    
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);
   }
}

說(shuō)明

  1. SeectorWrapper 選擇器封裝類

    這里我們?cè)讷@取選擇器時(shí),特意定義了一個(gè)封裝類肉微,其中包含具體的選擇器對(duì)象匾鸥,以及所匹配的參數(shù)類型,因此可以在下一步通過(guò)選擇器獲取實(shí)現(xiàn)類時(shí)碉纳,保證傳入的參數(shù)類型合法

  2. private SelectorWrapper initSelector(Class<? extends ISelector> clz) 具體的實(shí)例化選擇器的方法

    從實(shí)現(xiàn)來(lái)看勿负,優(yōu)先從選擇器緩存中獲取選擇器對(duì)象,這樣的目的是保證一個(gè)spi接口劳曹,每種類型的選擇器只有一個(gè)實(shí)例奴愉;因此在自定義選擇器中,你完全可以做一些選擇判斷的緩存邏輯铁孵,如 ParamsSelector 中的spi實(shí)現(xiàn)類的有序緩存列表

  3. currentSelector , currentMethodSelector, selectorInstanceCacheMap

     currentSelector:   對(duì)應(yīng)的是類選擇器锭硼,每個(gè)SPI接口必然會(huì)有一個(gè),作為打底的選擇器
     currentMethodSelector:  方法選擇器映射關(guān)系表蜕劝,key為方法名檀头,value為該方法對(duì)應(yīng)的選擇器; 所以spi接口中岖沛,不支持重載
     selectorInstanceCacheMap: spi接口所有定義的選擇器映射關(guān)系表暑始,key為選擇器類型,value是實(shí)例婴削;用于保障每個(gè)spi接口中選擇器只會(huì)有一個(gè)實(shí)例
    

3. 獲取實(shí)現(xiàn)類

對(duì)使用者而言廊镜,最關(guān)注的就是這個(gè)接口,這里會(huì)返回我們需要的實(shí)現(xiàn)類(or代理)馆蠕;內(nèi)部的邏輯也比較清楚期升,首先確定選擇器惊奇,然后通過(guò)選擇器便利所有的實(shí)現(xiàn)類,把滿足條件的返回即可

從上面的描述可以看到播赁,主要分為兩步

  1. 獲取選擇器
  2. 根據(jù)選擇器颂郎,遍歷所有的實(shí)現(xiàn)類,找出匹配的返回

獲取選擇器

初始化選擇器之后容为,我們會(huì)有 currentSelector , currentMethodSelector 兩個(gè)緩存

  • 靜態(tài)確定spi實(shí)現(xiàn)時(shí)乓序,直接用 currentSelector 即可 (spi接口中所有方法都公用類定義選擇器)
  • 動(dòng)態(tài)適配時(shí), 根據(jù)方法名在 currentMethodSelector 中獲取選擇器坎背,如果沒(méi)有替劈,則表示該方法沒(méi)有@SpiAdaptive注解,直接使用類的選擇器 currentMethodSelector 即可
// 動(dòng)態(tài)適配時(shí)得滤,獲取方法對(duì)應(yīng)對(duì)應(yīng)的selector實(shí)現(xiàn)邏輯
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());
  }

 // 參數(shù)不匹配時(shí)陨献,且傳入的參數(shù)為String類型, 則嘗試使用默認(rèn)選擇器進(jìn)行兼容(不建議在實(shí)現(xiàn)時(shí)懂更,出現(xiàn)這種場(chǎng)景)
  selector = DEFAULT_SELECTOR;
}

選擇實(shí)現(xiàn)類

這個(gè)的主要邏輯就是遍歷所有的實(shí)現(xiàn)類眨业,判斷是否滿足選擇器的條件,將第一個(gè)找到的返回即可沮协,所有的業(yè)務(wù)邏輯都在 ISelector 中實(shí)現(xiàn)龄捡,如下面給出的默認(rèn)選擇器,根據(jù)name來(lái)獲取實(shí)現(xiàn)類

/**
 * 默認(rèn)的根據(jù)name 獲取具體的實(shí)現(xiàn)類
 * <p/>
 * Created by yihui on 2017/5/24.
 */
public class DefaultSelector implements ISelector<String> {

    @Override
    public <K> K selector(Map<String, SpiImplWrapper<K>> map, String name) throws NoSpiMatchException {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("spiName should not be empty!");
        }

        if (map == null || map.size() == 0) {
            throw new IllegalArgumentException("no impl spi!");
        }


        if (!map.containsKey(name)) {
            throw new NoSpiMatchException("no spiImpl match the name you choose! your choose is: " + name);
        }

        return map.get(name).getSpiImpl();
    }

}

流程說(shuō)明

上面主要就各個(gè)點(diǎn)單獨(dú)的進(jìn)行了說(shuō)明慷暂,看起來(lái)可能比較分散聘殖,看完之后可能沒(méi)有一個(gè)清晰的流程,這里就整個(gè)實(shí)現(xiàn)的流程順一遍行瑞,主要從使用者的角度出發(fā)奸腺,當(dāng)定義了一個(gè)SPI接口后,到獲取spi實(shí)現(xiàn)的過(guò)程中蘑辑,上面的這些步驟是怎樣串在一起的

流程圖

先拿簡(jiǎn)單的靜態(tài)獲取SPI實(shí)現(xiàn)流程說(shuō)明(動(dòng)態(tài)的其實(shí)差不多洋机,具體的差異下一篇說(shuō)明)坠宴,先看下這種用法的使用姿勢(shì)

@Spi
public interface IPrint {
    void print(String str);
}

public class FilePrint implements IPrint {
    @Override
    public void print(String str) {
        System.out.println("file print: " + str);
    }
}

public class ConsolePrint implements IPrint {

    @Override
    public void print(String str) {
        System.out.println("console print: " + str);
    }
}

@Test
public void testPrint() throws NoSpiMatchException {
   SpiLoader<IPrint> spiLoader = SpiLoader.load(IPrint.class);
   IPrint print = spiLoader.getService("ConsolePrint");
   print.print("console---->");
}

SpiLoader<IPrint> spiLoader = SpiLoader.load(IPrint.class);

這行代碼觸發(fā)的action 主要是初始化所有的選擇器, 如下圖

  • 首先從緩存中查
  • 是否已經(jīng)初始化過(guò)了有則直接返回洋魂;
  • 緩存中沒(méi)有,則進(jìn)入new一個(gè)新的對(duì)象出來(lái)
    • 解析類上注解 @Spi喜鼓,初始化 currentSelector
    • 解析所有方法的注解 @SpiAdaptive 副砍, 初始化 currentMethodSelector
  • 塞入緩存,并返回
https://static.oschina.net/uploads/img/201705/27140821_19ee.png

IPrint print = spiLoader.getService("ConsolePrint");

根據(jù)name獲取實(shí)現(xiàn)類庄岖,具體流程如下

  • 判斷是否加載過(guò)所有實(shí)現(xiàn)類 spiImplClassCacheMap
  • 沒(méi)有加載豁翎,則重新加載所有的實(shí)現(xiàn)類
    • 通過(guò)jdk的 ServiceLoader.load() 方法獲取所有的實(shí)現(xiàn)類
    • 遍歷實(shí)現(xiàn)類,根據(jù) @SpiConf 注解初始化參數(shù)隅忿,封裝 SpiImplWrapper對(duì)象
    • 保存封裝的 SpiImplWrapper對(duì)象到緩存
  • 執(zhí)行 currentSelector.select() 方法心剥,獲取匹配的實(shí)現(xiàn)類
https://static.oschina.net/uploads/img/201705/27150620_EOUL.png

其他

博客系列鏈接:

-SPI框架實(shí)現(xiàn)之旅一:背景介紹
-SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)
-SPI框架實(shí)現(xiàn)之旅三:實(shí)現(xiàn)說(shuō)明
-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閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畦娄,死亡現(xiàn)場(chǎng)離奇詭異又沾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)熙卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)杖刷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人驳癌,你說(shuō)我怎么就攤上這事滑燃。” “怎么了颓鲜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵不瓶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我灾杰,道長(zhǎng)蚊丐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任艳吠,我火速辦了婚禮麦备,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昭娩。我一直安慰自己凛篙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布栏渺。 她就那樣靜靜地躺著呛梆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕诊。 梳的紋絲不亂的頭發(fā)上填物,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音霎终,去河邊找鬼滞磺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛莱褒,可吹牛的內(nèi)容都是我干的击困。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼广凸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼阅茶!你這毒婦竟也來(lái)了蛛枚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脸哀,失蹤者是張志新(化名)和其女友劉穎坤候,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體企蹭,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帖烘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绳矩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儒陨,死狀恐怖像鸡,靈堂內(nèi)的尸體忽然破棺而出喧兄,到底是詐尸還是另有隱情狼荞,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布闽寡,位于F島的核電站代兵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爷狈。R本人自食惡果不足惜植影,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涎永。 院中可真熱鬧思币,春花似錦、人聲如沸羡微。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)妈倔。三九已至博投,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盯蝴,已是汗流浹背毅哗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留结洼,地道東北人黎做。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓叉跛,卻偏偏與公主長(zhǎng)得像松忍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筷厘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理鸣峭,服務(wù)發(fā)現(xiàn)宏所,斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,156評(píng)論 25 707
  • SPI框架實(shí)現(xiàn)之旅三:實(shí)現(xiàn)說(shuō)明 前一篇 《SPI框架實(shí)現(xiàn)之旅二:整體設(shè)計(jì)》中摊溶,介紹了幾個(gè)定義的接口爬骤,注解;敘述了實(shí)...
    一灰灰blog閱讀 917評(píng)論 0 1
  • 時(shí)光肆意流淌 從來(lái)沒(méi)有停下輪轉(zhuǎn)的腳步 滴滴答答響過(guò)不停 像敲擊鍵盤(pán)的聲音 短促莫换、清翠霞玄,干凈、有力 似乎在向世人攫取...
    吳楓WF閱讀 326評(píng)論 3 1
  • 最近“江歌被殺案”新聞出現(xiàn)頻率較高陵叽,每天都能看到江歌媽媽的動(dòng)態(tài)∧現(xiàn)在的新聞為了爆點(diǎn)、熱度巩掺,會(huì)有夸張成分偏序,我怕我們看...
    安娜姑娘閱讀 678評(píng)論 0 0