日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747


聽過了API,那SPI是什么?一起來看看

 

引語

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

SPI理解

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

聽過了API,那SPI是什么?一起來看看

 

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

SPI的使用場景

SPI在框架中其實有很多廣泛的應用,這里列舉幾個例子: 1.MySQL驅動的選擇driverManager根據配置來確定要使用的驅動;

2.dubbo框架中的擴展機制

使用實例

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

1.首先定義一個接口,忍者服務接口

public interface NinjaService {
 void performTask();
}

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

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

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

4.創建一個Client場景類來調用看看結果

聽過了API,那SPI是什么?一起來看看

 

很完美的調用了兩個實現類的performTask()方法.

5.最后貼一下目錄結構

聽過了API,那SPI是什么?一起來看看

 

SPI源碼簡單分析

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

// 繼承了Iterable類 遍歷的時候使用
public final class ServiceLoader<S> implements Iterable<S>
{
 // 這就是為啥需要在META-INF/services/目錄下創建服務類的文件
 private static final String PREFIX = "META-INF/services/";
 // 被加載的服務
 private final Class<S> service;
 // 類加載器
 private final ClassLoader loader;
 // 訪問控制類
 private final AccessControlContext acc;
 // 實現類的緩存 根據初始化的順序 也就是在/services/文件中的定義順序來定義的加載順序
 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
 // 懶加載iterator
 private LazyIterator lookupIterator;

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

ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);

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

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

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

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

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

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

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

public boolean hasNext() {
 // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
 // ServiceLoader初始化沒有設置過securityManager,所以acc是null,進入hasNextService()
 if (acc == null) {
 return hasNextService();
 } else {
 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
 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下的文件 也就是含有兩個實現類全限定名的文件
 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;
 }
 // 這里是將上面加載的文件中的兩個實現類的文件
 pending = parse(service, configs.nextElement());
 }
 nextName = pending.next();
 return true;
 }

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

private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
 InputStream in = null;
 BufferedReader r = null;
 ArrayList<String> 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),檢查類名是否符合規范,符合的話添加到Iterator中,到這里var2 .hasNext()執行完畢,結果是加載了services下的文件內容

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
 List<String> 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());
 }
 // 這里將下一個實現類的名字賦值給了LazyIterator的屬性nextName
 nextName = pending.next();
 return true;
 }

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

private S nextService() {
 if (!hasNextService())
 throw new NoSuchElementException();
 String cn = nextName;
 nextName = null;
 Class<?> c = null;
 try {
 // 這里nextName在上面已經賦值過了 所以反射創建實例
 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 {
 // 強轉類型
 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.到這里子類的實現類返回,分析就結束了.

總結:

1.了解了什么是SPI;

2.SPI和API的簡單區別和聯系;

3.學習了怎么使用SPI來擴展服務;

4.分析了ServiceLoader的源碼加載過程,這里扯一句,簡單的就是META-INF/services定義好要實現的接口(文件名)和實現類(文件內容), ServiceLoader加載的時候沒有實例化實現類,而是在Iterator遍歷的時候去用反射創建了實例.
 

分享到:
標簽:SPI
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定