成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

聽過了API咱們來看看SPI是什么

wapeyang / 1971人閱讀

摘要:引語平時倒是聽得很多又是啥別急我們來先看看面向接口編程的調(diào)用關(guān)系,來了解一下,和的相似和不同之處。理解先來一段官話的介紹全稱為,是內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制聽了一臉懵逼好的,我們結(jié)合圖片來理解一下。然后調(diào)用了內(nèi)部類的方法。

引語

平時API倒是聽得很多?SPI又是啥.別急我們來先看看面向接口編程的調(diào)用關(guān)系,來了解一下,API和SPI的相似和不同之處。

SPI理解

先來一段官話的介紹:SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制.(聽了一臉懵逼)好的,我們結(jié)合圖片來理解一下。

????簡單的來說分為調(diào)用方,接口,服務(wù)方.接口就是協(xié)議,契約,可以調(diào)用方定義,也可以由服務(wù)方定義,也就是接口是可以位于調(diào)用方的包或者服務(wù)方的包.
1.接口的定義和實現(xiàn)都在服務(wù)方的時候,僅暴露出接口給調(diào)用方使用的時候,我們稱為API;
2.接口的定義在調(diào)用方的時候(實現(xiàn)在服務(wù)方),我們給它也取個名字--SPI。
應(yīng)該還比較好理解吧?

SPI的使用場景

SPI在框架中其實有很多廣泛的應(yīng)用,這里列舉幾個例子:
1.Mysql驅(qū)動的選擇driverManager根據(jù)配置來確定要使用的驅(qū)動;
2.dubbo框架中的擴展機制(dubbo官網(wǎng)鏈接)

使用實例

看完上面的簡介和SPI在框架中的應(yīng)用,想必對SPI在讀者的大腦中已經(jīng)產(chǎn)生了一個雛形,talk is cheap!show me the code.說了這么多,我們具體寫一個簡單的例子來看看效果,驗證一下SPI.

1.首先定義一個接口,忍者服務(wù)接口

public interface NinjaService {
    void performTask();
}

2.接下來寫兩個實現(xiàn)類,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍)

public class ForbearanceServiceImpl implements NinjaService {

    @Override
    public void performTask() {
        System.out.println("上忍在執(zhí)行A級任務(wù)");
    }
}
public class ShinobuServiceImpl implements NinjaService {

    @Override
    public void performTask() {
        System.out.println("下忍在執(zhí)行D級任務(wù)");
    }
}

3.接下來我們在main/resources/下創(chuàng)建META-INF/services目錄,并且在services目錄下創(chuàng)建一個com.scott.java.task.spi.NinjaService(忍者服務(wù)類的全限定名)的文件.

4.創(chuàng)建一個Client場景類來調(diào)用看看結(jié)果

很完美的調(diào)用了兩個實現(xiàn)類的performTask()方法.

5.最后貼一下目錄結(jié)構(gòu)

附上一波代碼例子的地址,在spi里面,git鏈接;

SPI源碼簡單分析

1.先看下核心類ServiceLoader的定義和屬性

// 繼承了Iterable類  遍歷的時候使用
public final class ServiceLoader implements Iterable
{
  // 這就是為啥需要在META-INF/services/目錄下創(chuàng)建服務(wù)類的文件
  private static final String PREFIX = "META-INF/services/";

  // 被加載的服務(wù)
  private final Class service;

  // 類加載器
  private final ClassLoader loader;

  // 訪問控制類
  private final AccessControlContext acc;

  // 實現(xiàn)類的緩存 根據(jù)初始化的順序 也就是在/services/文件中的定義順序來定義的加載順序
  private LinkedHashMap providers = new LinkedHashMap<>();

  // 懶加載iterator
  private LazyIterator lookupIterator;

2.然后從client開始,然后依次debug進去

ServiceLoader ninjaServices = ServiceLoader.load(NinjaService.class);
public static  ServiceLoader load(Class service) {
      // 獲取當(dāng)前的類加載器 也就是AppClassLoader
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return ServiceLoader.load(service, cl);
  }
public static  ServiceLoader load(Class service, ClassLoader loader) {
       return new ServiceLoader<>(service, loader);
   }

后面的就省略了,因為這里僅僅就是根據(jù)NinjaService初始化的事項,沒有什么很難理解的點.

3.我們在看看具體的調(diào)用過程,這里使用的是client對應(yīng)的class文件,因為增加for(foreach)在java中是個語法糖,實際上編譯后是這樣的內(nèi)容

public static void main(String[] args) {
       ServiceLoader ninjaServices = ServiceLoader.load(NinjaService.class);
      // 這里一下其實就是增加for解糖后的代碼 有興趣可以去了解下java的語法糖
       Iterator var2 = ninjaServices.iterator();

       while(var2.hasNext()) {
           NinjaService item = (NinjaService)var2.next();
           item.performTask();
       }
   }

4.隨著斷點繼續(xù)走,我們進入到var2.hasNext()的方法

public boolean hasNext() {
            // knownProviders還沒有加載過provider 走下面的分支
             if (knownProviders.hasNext())
                 return true;
             return lookupIterator.hasNext();
         }

這里lookupIterator上面ServiceLoader的屬性介紹過,它其實是ServiceLoader中的一個Iterator的內(nèi)部類。然后調(diào)用了內(nèi)部類Iterator的hasNext()方法。

public boolean hasNext() {
          //   acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
          //   ServiceLoader初始化沒有設(shè)置過securityManager,所以acc是null,進入hasNextService()
           if (acc == null) {
               return hasNextService();
           } else {
               PrivilegedAction action = new PrivilegedAction() {
                   public Boolean run() { return hasNextService(); }
               };
               return AccessController.doPrivileged(action, acc);
           }
       }

5.hasNextService()分析

private boolean hasNextService() {
           if (nextName != null) {
               return true;
           }
           if (configs == null) {
               try {
               // 這里加載了META-INF/services下的文件 也就是含有兩個實現(xiàn)類全限定名的文件
                   String fullName = PREFIX + service.getName();
                   if (loader == null)
                       configs = ClassLoader.getSystemResources(fullName);
                   else
                   // 因為loader是不為null 的AppClassLoader
                       configs = loader.getResources(fullName);
               } catch (IOException x) {
                   fail(service, "Error locating configuration files", x);
               }
           }
           while ((pending == null) || !pending.hasNext()) {
               if (!configs.hasMoreElements()) {
                   return false;
               }
               // 這里是將上面加載的文件中的兩個實現(xiàn)類的文件
               pending = parse(service, configs.nextElement());
           }
           nextName = pending.next();
           return true;
       }

6.繼續(xù)看到parse方法,這里最后返回的是含有兩個全限定類名的Iterator,其實就是把services/下的文件內(nèi)容給加載出來

private Iterator parse(Class service, URL u) throws ServiceConfigurationError {
   InputStream in = null;
   BufferedReader r = null;
   ArrayList names = new ArrayList<>();
   try {
       in = u.openStream();
       r = new BufferedReader(new InputStreamReader(in, "utf-8"));
       int lc = 1;
       while ((lc = parseLine(service, u, r, lc, names)) >= 0);
   } catch (IOException x) {
       fail(service, "Error reading configuration file", x);
   } finally {
       try {
           if (r != null) r.close();
           if (in != null) in.close();
       } catch (IOException y) {
           fail(service, "Error closing configuration file", y);
       }
   }
   return names.iterator();
}

附帶的說一下parseLine(service, u, r, lc, names),檢查類名是否符合規(guī)范,符合的話添加到Iterator中,到這里var2
.hasNext()執(zhí)行完畢,結(jié)果是加載了services下的文件內(nèi)容

private int parseLine(Class service, URL u, BufferedReader r, int lc,
                         List names)
       throws IOException, ServiceConfigurationError
   {
       String ln = r.readLine();
       if (ln == null) {
           return -1;
       }
       int ci = ln.indexOf("#");
       if (ci >= 0) ln = ln.substring(0, ci);
       ln = ln.trim();
       int n = ln.length();
       if (n != 0) {
           if ((ln.indexOf(" ") >= 0) || (ln.indexOf("	") >= 0))
               fail(service, u, lc, "Illegal configuration-file syntax");
           int cp = ln.codePointAt(0);
           if (!Character.isJavaIdentifierStart(cp))
               fail(service, u, lc, "Illegal provider-class name: " + ln);
           for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
               cp = ln.codePointAt(i);
               if (!Character.isJavaIdentifierPart(cp) && (cp != "."))
                   fail(service, u, lc, "Illegal provider-class name: " + ln);
           }
           if (!providers.containsKey(ln) && !names.contains(ln))
               names.add(ln);
       }
       return lc + 1;
   }
private boolean hasNextService() {
          if (nextName != null) {
              return true;
          }
          if (configs == null) {
              try {
                  String fullName = PREFIX + service.getName();
                  if (loader == null)
                      configs = ClassLoader.getSystemResources(fullName);
                  else
                      configs = loader.getResources(fullName);
              } catch (IOException x) {
                  fail(service, "Error locating configuration files", x);
              }
          }
          while ((pending == null) || !pending.hasNext()) {
              if (!configs.hasMoreElements()) {
                  return false;
              }
              pending = parse(service, configs.nextElement());
          }
          // 這里將下一個實現(xiàn)類的名字賦值給了LazyIterator的屬性nextName
          nextName = pending.next();
          return true;
      }

7.接下來執(zhí)行的是 NinjaService item = (NinjaService)var2.next()的next(方法),然后繼續(xù)debug進去,這里我省略了一些方法的調(diào)用,只展示出有用的方法這個是ServiceLoader的內(nèi)部類LazyIterator的nextService()方法.

private S nextService() {
           if (!hasNextService())
               throw new NoSuchElementException();
           String cn = nextName;
           nextName = null;
           Class c = null;
           try {
           // 這里nextName在上面已經(jīng)賦值過了 所以反射創(chuàng)建實例
               c = Class.forName(cn, false, loader);
           } catch (ClassNotFoundException x) {
               fail(service,
                    "Provider " + cn + " not found");
           }
           // 類型判斷
           if (!service.isAssignableFrom(c)) {
               fail(service,
                    "Provider " + cn  + " not a subtype");
           }
           try {
           // 強轉(zhuǎn)類型
               S p = service.cast(c.newInstance());
          //  將類添加到ServiceLoader的providers屬性中 然后返回
               providers.put(cn, p);
               return p;
           } catch (Throwable x) {
               fail(service,
                    "Provider " + cn + " could not be instantiated",
                    x);
           }
           throw new Error();          // This cannot happen
       }

8.到這里子類的實現(xiàn)類返回,分析就結(jié)束了.

總結(jié):

1.了解了什么是SPI;
2.SPI和API的簡單區(qū)別和聯(lián)系;
3.學(xué)習(xí)了怎么使用SPI來擴展服務(wù);
4.分析了ServiceLoader的源碼加載過程,這里扯一句,簡單的就是META-INF/services定義好要實現(xiàn)的接口(文件名)和實現(xiàn)類(文件內(nèi)容),
ServiceLoader加載的時候沒有實例化實現(xiàn)類,而是在Iterator遍歷的時候去用反射創(chuàng)建了實例.

覺得寫得還行的可以點個贊,關(guān)注一波,后面會繼續(xù)寫更好的文章~ XD

參考資料:

1.http://cr.openjdk.java.net/~m...
2.https://www.cnblogs.com/happy...
3.http://dubbo.apache.org/zh-cn...
4.https://www.cnblogs.com/googl...
5.https://zhuanlan.zhihu.com/p/...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77771.html

相關(guān)文章

  • dubbo之SPI自適應(yīng)擴展機制

    摘要:對于這個矛盾的問題,通過自適應(yīng)拓展機制很好的解決了。自適應(yīng)拓展機制的實現(xiàn)邏輯比較復(fù)雜,首先會為拓展接口生成具有代理功能的代碼。 1、背景 在 Dubbo 中,很多拓展都是通過 SPI 機制進行加載的,比如 Protocol、Cluster、LoadBalance 等。有時,有些拓展并不想在框架啟動階段被加載,而是希望在拓展方法被調(diào)用時,根據(jù)運行時參數(shù)進行加載。這聽起來有些矛盾。拓展未被...

    vvpale 評論0 收藏0
  • dubbo源碼解析(八)遠程通信——開篇

    摘要:而編碼器是講應(yīng)用程序的數(shù)據(jù)轉(zhuǎn)化為網(wǎng)絡(luò)格式,解碼器則是講網(wǎng)絡(luò)格式轉(zhuǎn)化為應(yīng)用程序,同時具備這兩種功能的單一組件就叫編解碼器。在中是老的編解碼器接口,而是新的編解碼器接口,并且已經(jīng)用把適配成了。 遠程通訊——開篇 目標:介紹之后解讀遠程通訊模塊的內(nèi)容如何編排、介紹dubbo-remoting-api中的包結(jié)構(gòu)設(shè)計以及最外層的的源碼解析。 前言 服務(wù)治理框架中可以大致分為服務(wù)通信和服務(wù)管理兩個...

    Faremax 評論0 收藏0
  • 聽閏土大話前端之ES6怎么來的

    摘要:前言相信做前端的朋友沒有不知道的,都知曉新增了不少新的特性,但是你知道是怎么來的嗎今天就讓閏土來帶大家大話的前世今生。之前可能是自己娛樂為主,大家來旁觀為輔。還有一個比較大的版本就是,它是年正式誕生的。大話前端系列文章較長,未完待續(xù)。 showImg(https://segmentfault.com/img/bV4pck?w=1280&h=693); 前言 相信做前端的朋友沒有不知道E...

    BaronZhang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<