ServiceLoader使用看這一篇就夠了

最近比較流行起一個(gè)比較牛逼的題目岁钓,蹭個(gè)熱點(diǎn),可能沒那么牛逼偎巢,可是對(duì)于使用和了解原理是足夠了蔼夜。

??想必大家多多少少聽過spi,具體的解釋我就不多說了压昼。但是它具體是怎么實(shí)現(xiàn)的呢求冷?它的原理是什么呢?下面我就圍繞這兩個(gè)問題來解釋:


實(shí)現(xiàn): 其實(shí)具體的實(shí)現(xiàn)類就是java.util.ServiceLoader這個(gè)類窍霞。


??要想了解一個(gè)機(jī)制的原理匠题,首先得知道它是怎么運(yùn)行的,需要什么配置但金,才能運(yùn)行起來韭山。然后再分解來了解實(shí)現(xiàn)。對(duì)于技術(shù)實(shí)現(xiàn)也是一樣,先看這個(gè)類是怎么實(shí)現(xiàn)的钱磅,先讓它跑起來巩踏,看到效果。然后再講原理续搀。
按照使用說明文檔,應(yīng)該分下面幾個(gè)步驟來使用:

  1. 創(chuàng)建一個(gè)接口文件
  2. 在resources資源目錄下創(chuàng)建META-INF/services文件夾
  3. 在services文件夾中創(chuàng)建文件菠净,以接口全名命名
  4. 創(chuàng)建接口實(shí)現(xiàn)類

我們想測(cè)試一下禁舷,一般是在這個(gè)工程中建立一個(gè)測(cè)試類來測(cè)試。來看下代碼片段:
接口:

public interface IMyServiceLoader {

    String sayHello();

    String getName();
}

實(shí)現(xiàn)類:

public class MyServiceLoaderImpl1 implements IMyServiceLoader {
    @Override
    public String sayHello() {
        return "hello1";
    }

    @Override
    public String getName() {
        return "name1";
    }
}

public class MyServiceLoaderImpl2 implements IMyServiceLoader {
    @Override
    public String sayHello() {
        return "hello2";
    }

    @Override
    public String getName() {
        return "name2";
    }
}

測(cè)試類:

public class TestMyServiceLoader {
    public static void main(String[] argus){
        ServiceLoader<IMyServiceLoader> serviceLoader = ServiceLoader.load(IMyServiceLoader.class);
        for (IMyServiceLoader myServiceLoader : serviceLoader){
            System.out.println(myServiceLoader.getName() + myServiceLoader.sayHello());
        }
    }
}

正常情況下這里應(yīng)該輸出

name2hello2
name1hello1

看了這些步驟毅往,想必你也知道原理了牵咙,我在這里總結(jié)下。


原理:在ServiceLoader.load的時(shí)候攀唯,根據(jù)傳入的接口類洁桌,遍歷META-INF/services目錄下的以該類命名的文件中的所有類,并實(shí)例化返回侯嘀。


??相信看到這里另凌,有的看客該爆粗話了,說啥子看著一篇就夠了戒幔,這些知識(shí)點(diǎn)隨便一搜吠谢,到處都是好伐。是的诗茎,上面說的工坊,確實(shí)隨便一搜都可以搜到,所以這里我要?jiǎng)澲攸c(diǎn)了:

問題

??上面說了敢订,正常情況下會(huì)那樣輸出王污,但是你運(yùn)行程序你就會(huì)發(fā)現(xiàn),馬丹楚午,怎么不起作用啊昭齐,我哪里做錯(cuò)了,都是按照文章步驟來做的醒叁。弄的你都開始懷疑人生了司浪。不要懷疑人生,在一個(gè)工程中做測(cè)試把沼,確實(shí)不能實(shí)現(xiàn)想要的效果啊易。

回憶場(chǎng)景

回憶一下spi的使用場(chǎng)景。它是給制作標(biāo)準(zhǔn)的一放用的饮睬,用來指定標(biāo)準(zhǔn)租谈,然后不同實(shí)現(xiàn)方,用不同的方式實(shí)現(xiàn)標(biāo)準(zhǔn)供使用方使用。那標(biāo)準(zhǔn)方和實(shí)現(xiàn)方必然不是一個(gè)割去。想到這里窟却,你應(yīng)該能夠向明白了吧。

解決方案

要解決問題呻逆,就把之前做的打jar包夸赫,引入新工程測(cè)試,這樣就可以了咖城。

疑問

但是有人會(huì)說標(biāo)準(zhǔn)方和實(shí)現(xiàn)方也可能是一個(gè)啊茬腿,好比標(biāo)準(zhǔn)方我提供一個(gè)內(nèi)部的實(shí)現(xiàn)方案也是可以的啊。也確實(shí)有道理啊宜雀,那這種怎么實(shí)現(xiàn)呢切平?

思考

當(dāng)然也有辦法,下面就說下實(shí)現(xiàn)方法辐董。想要實(shí)現(xiàn)上面的需求悴品,首先要知道攔阻這個(gè)需求實(shí)現(xiàn)的問題,然后把這些問題都解決了简烘,需求自然也就實(shí)現(xiàn)了苔严。那就先來分析問題吧,為什么在一個(gè)工程中獲取不到接口的實(shí)現(xiàn)類呢夸研?經(jīng)過觀察發(fā)現(xiàn)是因?yàn)橘Y源文件沒有在classPath中邦蜜,為什么這么說呢,可以看下build的目錄下面是沒有META-INF文件夾『ブ粒現(xiàn)在知道了原因悼沈,這么解決呢?

疑問臨時(shí)解決方案

最簡(jiǎn)單的方法姐扮,把資源下的META-INF文件夾拷貝到build目錄下絮供,然后再運(yùn)行,發(fā)現(xiàn)可以了茶敏,這也就驗(yàn)證了壤靶,確實(shí)是這個(gè)問題造成的。搞定惊搏!


再次發(fā)出疑問

這樣就結(jié)束了贮乳,那我總不能手動(dòng)拷貝吧,這不算解決方案恬惯,只是臨時(shí)方案向拆。那要怎么解決呢?

??我就不賣關(guān)子了酪耳。其實(shí)要解決這個(gè)問題浓恳,只要在編譯的時(shí)候把這些文件放到build目錄中就行了,是不是很簡(jiǎn)單。思路是有了颈将,可是怎么實(shí)現(xiàn)呢梢夯?這個(gè)時(shí)候要用到攔截編譯處理,然后再里面做這件事情晴圾。

方案一
繼承AbsStractProcessor颂砸,在process方法中把資源文件移到build目錄下。

方案二
這里用到了google開源的AutoService

大概看了下autoService的源碼死姚,其實(shí)它也是使用方案一的方法沾凄,攔截編譯過程,然后再build目錄下生成配置文件知允,這里來大概看下它的process方法:

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    try {
      return processImpl(annotations, roundEnv);
    } catch (Exception e) {
      ...
      return true;
    }
  }

private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    if (roundEnv.processingOver()) {
      generateConfigFiles();
    } else {
      processAnnotations(annotations, roundEnv);
    }
    return true;
  }

這里你會(huì)發(fā)現(xiàn)其實(shí)就是generateConfigFiles()processAnnotations(annotations, roundEnv)看名字可以猜到processAnnotations是處理注解的,這里實(shí)現(xiàn)類都實(shí)現(xiàn)了注解叙谨,所以這里應(yīng)該是找到實(shí)現(xiàn)類温鸽。

private void processAnnotations(Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {

    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
    for (Element e : elements) {
      TypeElement providerImplementer = (TypeElement) e;
      AnnotationMirror providerAnnotation = getAnnotationMirror(e, AutoService.class).get();
      DeclaredType providerInterface = getProviderInterface(providerAnnotation);
      TypeElement providerType = (TypeElement) providerInterface.asElement();
      ...
      String providerTypeName = getBinaryName(providerType);
      String providerImplementerName = getBinaryName(providerImplementer);
      providers.put(providerTypeName, providerImplementerName);
    }
  }

確實(shí)如此,這里會(huì)把所有的實(shí)現(xiàn)類存起來手负。


再來看看generateConfigFiles()方法

private void generateConfigFiles() {
    Filer filer = processingEnv.getFiler();

    for (String providerInterface : providers.keySet()) {
      String resourceFile = "META-INF/services/" + providerInterface;
      try {
        SortedSet<String> allServices = Sets.newTreeSet();
        try {
          FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
              resourceFile);
          Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
          allServices.addAll(oldServices);
        } catch (IOException e) {
        }

        Set<String> newServices = new HashSet<String>(providers.get(providerInterface));

        allServices.addAll(newServices);
        FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
            resourceFile);
        OutputStream out = fileObject.openOutputStream();
        ServicesFiles.writeServiceFile(allServices, out);
        out.close();
      } catch (IOException e) {
        return;
      }
    }
  }

這里是在build下創(chuàng)建META-INF目錄涤垫。和我們想的一模一樣。


總結(jié)

好了竟终,要實(shí)現(xiàn)文章開頭的需求蝠猬,除非你覺得你比google開源AutoService的工程師寫的更好,不然就直接使用AutoService吧统捶。這篇文章不僅是分析ServiceLoader的原理榆芦,實(shí)現(xiàn)我們的需求,更重要的是高速我們遇到問題該怎么分析問題喘鸟,解決問題匆绣。

擴(kuò)展

其實(shí)還有很多比較好玩的,比如在攔截到編譯過程時(shí)什黑,可以再編譯期生成一些有意思的代碼崎淳,來幫我們實(shí)現(xiàn)一些自動(dòng)化處理。這就需要?jiǎng)佑梦覀兊拇竽X就想了愕把,介紹一下生成代碼的庫(kù)javapoet拣凹,大家可以了解一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恨豁,一起剝皮案震驚了整個(gè)濱河市嚣镜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌圣絮,老刑警劉巖祈惶,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捧请,警方通過查閱死者的電腦和手機(jī)凡涩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疹蛉,“玉大人活箕,你說我怎么就攤上這事】煽睿” “怎么了育韩?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)闺鲸。 經(jīng)常有香客問我筋讨,道長(zhǎng),這世上最難降的妖魔是什么摸恍? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任悉罕,我火速辦了婚禮,結(jié)果婚禮上立镶,老公的妹妹穿的比我還像新娘壁袄。我一直安慰自己,他們只是感情好媚媒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布嗜逻。 她就那樣靜靜地躺著,像睡著了一般缭召。 火紅的嫁衣襯著肌膚如雪栈顷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天嵌巷,我揣著相機(jī)與錄音妨蛹,去河邊找鬼。 笑死晴竞,一個(gè)胖子當(dāng)著我的面吹牛蛙卤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播噩死,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼颤难,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了已维?” 一聲冷哼從身側(cè)響起行嗤,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎垛耳,沒想到半個(gè)月后栅屏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飘千,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年栈雳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了护奈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哥纫,死狀恐怖霉旗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛀骇,我是刑警寧澤厌秒,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站擅憔,受9級(jí)特大地震影響鸵闪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暑诸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一岛马、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屠列,春花似錦、人聲如沸伞矩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乃坤。三九已至苛让,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湿诊,已是汗流浹背狱杰。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厅须,地道東北人仿畸。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像朗和,于是被迫代替她去往敵國(guó)和親错沽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理眶拉,服務(wù)發(fā)現(xiàn)千埃,斷路器,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342
  • 有許久許久沒寫過文章了忆植。 這次去三亞游玩準(zhǔn)備了一個(gè)多月放可,話不多說谒臼,直入主題。 Day1: 9/23日晚上10點(diǎn)多從...
    羅十殿閱讀 416評(píng)論 0 1
  • 愛是什么耀里,沒有人能解釋明白蜈缤。因?yàn)檫@是一種感覺,一種自然的不能在自然的感覺备韧。 在我看來劫樟,秋就是一場(chǎng)童話,一場(chǎng)夢(mèng)织堂,但卻...
    白小源家的燕砸閱讀 232評(píng)論 0 0