最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中SPI的一些理解

 更新時間:2020年12月07日 09:00:36   作者:紀(jì)莫  
這篇文章主要介紹了Java中SPI的一些理解,幫助大家更好的理解和學(xué)習(xí)Java的相關(guān)知識,感興趣的朋友可以了解下

前言

最近在面試的時候被問到SPI了,沒回答上來,主要也是自己的原因,把自己給帶溝里去了,因為講到了類加載器的雙親委派模型,后面就被問到了有哪些是破壞了雙親委派模型的場景,然后我就說到了SPI,JNDI,以及JDK9的模塊化都破壞了雙親委派。
然后就被問,那你說說對Java中的SPI的理解吧。然后我就一臉懵逼了,之前只是知道它會破壞雙親委派,也知道是個怎么回事,但是并沒有深入了解,那么這次我就好好的來總結(jié)一下這個知識吧。

什么是SPI

SPI全稱Service Provider Interface,字面意思是提供服務(wù)的接口,再解釋詳細(xì)一下就是Java提供的一套用來被第三方實現(xiàn)或擴(kuò)展的接口,實現(xiàn)了接口的動態(tài)擴(kuò)展,讓第三方的實現(xiàn)類能像插件一樣嵌入到系統(tǒng)中。

咦。。。
這個解釋感覺還是有點繞口。
那就說一下它的本質(zhì)。

將接口的實現(xiàn)類的全限定名配置在文件中(文件名是接口的全限定名),由服務(wù)加載器讀取配置文件,加載實現(xiàn)類。實現(xiàn)了運行時動態(tài)為接口替換實現(xiàn)類。

SPI示例

還是舉例說明吧。
我們創(chuàng)建一個項目,然后創(chuàng)建一個module叫spi-interface。

在這個module中我們定義一個接口:

/**
 * @author jimoer
 **/
public interface SpiInterfaceService {

  /**
   * 打印參數(shù)
   * @param parameter 參數(shù)
   */
  void printParameter(String parameter);
}

再定義一個module,名字叫spi-service-one,pom.xml中依賴spi-interface。
在spi-service-one中定義一個實現(xiàn)類,實現(xiàn)SpiInterfaceService 接口。

package com.jimoer.spi.service.one;
import com.jimoer.spi.app.SpiInterfaceService;

/**
 * @author jimoer
 **/
public class SpiOneService implements SpiInterfaceService {
  /**
   * 打印參數(shù)
   *
   * @param parameter 參數(shù)
   */
  @Override
  public void printParameter(String parameter) {
    System.out.println("我是SpiOneService:"+parameter);
  }
}

然后再spi-service-one的resources目錄下創(chuàng)建目錄META-INF/services,在此目錄下創(chuàng)建一個文件名稱為SpiInterfaceService接口的全限定名稱,文件內(nèi)容寫入SpiOneService這個實現(xiàn)類的全限定名稱。
效果如下:

再創(chuàng)建一個module,名稱為:spi-service-one,也是依賴spi-interface,并且定義一個實現(xiàn)類SpiTwoService 來實現(xiàn)SpiInterfaceService 接口。

package com.jimoer.spi.service.two;
import com.jimoer.spi.app.SpiInterfaceService;
/**
 * @author jimoer
 **/
public class SpiTwoService implements SpiInterfaceService {
  /**
   * 打印參數(shù)
   *
   * @param parameter 參數(shù)
   */
  @Override
  public void printParameter(String parameter) {
    System.out.println("我是SpiTwoService:"+parameter);
  }
}

目錄結(jié)構(gòu)如下:

下面再創(chuàng)建一個用來測試的module,名為:spi-app。

pom.xml中依賴spi-service-onespi-service-two

<dependencies>
  <dependency>
    <groupId>com.jimoer.spi</groupId>
    <artifactId>spi-service-one</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>com.jimoer.spi</groupId>
    <artifactId>spi-service-two</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>

創(chuàng)建測試類

/**
 * @author jimoer
 **/
public class SpiService {

  public static void main(String[] args) {

    ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);
    Iterator<SpiInterfaceService> iterator = spiInterfaceServices.iterator();
    while (iterator.hasNext()){
      SpiInterfaceService sip = iterator.next();
      sip.printParameter("參數(shù)");
    }
  }
}

執(zhí)行結(jié)果:

我是SpiTwoService:參數(shù)
我是SpiOneService:參數(shù)

通過運行結(jié)果我們可以看到,已經(jīng)將SpiInterfaceService接口的所有實現(xiàn)都加載到了當(dāng)前項目中,并且執(zhí)行了調(diào)用。

這整個代碼結(jié)構(gòu)我們可以看出SPI機制將模塊的裝配放到了程序外面,就是說,接口的實現(xiàn)可以在程序外面,只需要在使用的時候指定具體的實現(xiàn)。并且動態(tài)的加載到自己的項目中。
SPI機制的主要目的:
一是為了解耦,將接口和具體實現(xiàn)分離開來;
二是提高框架的擴(kuò)展性。以前寫程序的時候,接口和實現(xiàn)都寫在一起,調(diào)用方在使用的時候依賴接口來進(jìn)行調(diào)用,無權(quán)選擇使用具體的實現(xiàn)類。

SPI的實現(xiàn)

那么我們來看一下SPI具體是如何實現(xiàn)的呢?
通過上面的例子,我們可以看到,SPI機制的核心代碼是下面這段:

ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);

那么我們來看一下ServiceLoader.load()方法的源碼:

public static <S> ServiceLoader<S> load(Class<S> service) {
  ClassLoader cl = Thread.currentThread().getContextClassLoader();
  return ServiceLoader.load(service, cl);
}

看到Thread.currentThread().getContextClassLoader();我就明白是怎么回事了,這個就是線程上下文類加載器,因為線程上下文類加載器就是為了做類加載雙親委派模型的逆序而創(chuàng)建的。

使用這個線程上下文類加載器去加載所需的SPI服務(wù)代碼,這是一種父類加載器去請求子類加載器完成類加載的行為,這種行為實際上是打通了,雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,已經(jīng)違背了雙親委派模型的一般性原則,但也是無可奈何的事情。
深入理解Java虛擬機(第三版)

雖然知道了它是破壞雙親委派的了,但是具體實現(xiàn),還是需要具體往下看的。

在ServiceLoader里找到具體實現(xiàn)hasNext()的方法了,那么繼續(xù)來看這個方法的實現(xiàn)。

hasNext()方法又主要調(diào)用了hasNextService()方法。

// 固定路徑
private static final String PREFIX = "META-INF/services/";

private boolean hasNextService() {
   if (nextName != null) {
     return true;
   }
   if (configs == null) {
     try {
     	// 固定路徑+接口全限定名稱
       String fullName = PREFIX + service.getName();
       // 如果當(dāng)前線程上下文類加載器為空,會用父類加載器(默認(rèn)是應(yīng)用程序類加載器)
       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());
   }
   // 后面next()方法中判斷當(dāng)前類是否已經(jīng)出現(xiàn)化的時候要用
   nextName = pending.next();
   return true;
 }

主要就是去加載META-INF/services/路徑下的接口全限定名稱的文件然后去里面找到實現(xiàn)類的類路徑將實現(xiàn)類進(jìn)行類加載。

繼續(xù)看迭代器是如何取出每一個實現(xiàn)對象的。那就要看ServiceLoader中實現(xiàn)了迭代器的next()方法了。

next()方法主要是nextService()實現(xiàn)的,那么繼續(xù)看nextService()方法。

private S nextService() {
   if (!hasNextService())
     throw new NoSuchElementException();
   String cn = nextName;
   nextName = null;
   Class<?> c = null;
   try {
   // 直接加載類,無需初始化(因為上面hasNext()已經(jī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 {
   	// 將加載好的類實例化出對象。
     S p = service.cast(c.newInstance());
     providers.put(cn, p);
     return p;
   } catch (Throwable x) {
     fail(service,
       "Provider " + cn + " could not be instantiated",
       x);
   }
   throw new Error();     // This cannot happen
 }

看到這里就可以明白了,是如何創(chuàng)建出對象的了。先在hasNext()將接口的實現(xiàn)類進(jìn)行加載并判斷是否存在接口的實現(xiàn)類,然后在next()方法中將實現(xiàn)類進(jìn)實例化。

Java中使用SPI機制的功能其實有很多,像JDBC、JNDI、以及Spring中也有使用,甚至RPC框架(Dubbo)中也有使用SPI機制來實現(xiàn)功能。

以上就是Java中SPI的一些理解的詳細(xì)內(nèi)容,更多關(guān)于Java SPI的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于springboot實現(xiàn)redis分布式鎖的方法

    基于springboot實現(xiàn)redis分布式鎖的方法

    這篇文章主要介紹了基于springboot實現(xiàn)redis分布式鎖的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 如何搭建一個完整的Java開發(fā)環(huán)境

    如何搭建一個完整的Java開發(fā)環(huán)境

    這篇文章主要教大家如何搭建一個完整的Java開發(fā)環(huán)境,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Java中的LinkedHashSet解析

    Java中的LinkedHashSet解析

    這篇文章主要介紹了Java中的LinkedHashSet解析,與HashSet不同的是,LinkedHashSet在內(nèi)部使用了一個雙向鏈表來維護(hù)元素的順序,因此它可以保持元素的插入順序,這使得LinkedHashSet在需要保持元素順序的場景下非常有用,需要的朋友可以參考下
    2023-11-11
  • Java反射機制的適用場景及利弊詳解

    Java反射機制的適用場景及利弊詳解

    這篇文章主要介紹了Java反射機制的適用場景及利弊詳解,Spring用到很多反射機制,在xml文件或者properties里面寫好了配置,然后在Java類里面解析xml或properties里面的內(nèi)容,得到一個字符串,然后用反射機制,需要的朋友可以參考下
    2023-08-08
  • java后臺調(diào)用HttpURLConnection類模擬瀏覽器請求實例(可用于接口調(diào)用)

    java后臺調(diào)用HttpURLConnection類模擬瀏覽器請求實例(可用于接口調(diào)用)

    這篇文章主要介紹了java后臺調(diào)用HttpURLConnection類模擬瀏覽器請求實例,該實例可用于接口調(diào)用,具有一定的實用價值,需要的朋友可以參考下
    2014-10-10
  • 異常點/離群點檢測算法——LOF解析

    異常點/離群點檢測算法——LOF解析

    這篇文章主要介紹了異常點/離群點檢測算法——LOF解析,通過圖解文字描述的方式詳細(xì)的解析了該算法,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • Spring中的PathVariable注釋解析

    Spring中的PathVariable注釋解析

    這篇文章主要介紹了Spring中的PathVariable注釋用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • java服務(wù)端微信APP支付接口詳解

    java服務(wù)端微信APP支付接口詳解

    這篇文章主要為大家詳細(xì)介紹了java服務(wù)端微信APP支付接口,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • 深入淺析Java中的final關(guān)鍵字

    深入淺析Java中的final關(guān)鍵字

    在Java中,final關(guān)鍵字可以用來修飾類、方法和變量(包括成員變量和局部變量),下面通過本篇文章給大家介紹java中的final關(guān)鍵字,對java fina關(guān)鍵字相關(guān)知識感興趣的朋友一起看看吧
    2015-12-12
  • Java求兩個正整數(shù)的最大公約數(shù)和最小公倍數(shù)

    Java求兩個正整數(shù)的最大公約數(shù)和最小公倍數(shù)

    這篇文章主要介紹了輸入兩個正整數(shù)m和n,求其最大公約數(shù)和最小公倍數(shù),需要的朋友可以參考下
    2017-02-02

最新評論

东辽县| 竹山县| 防城港市| 屯门区| 察隅县| 郴州市| 大埔县| 乃东县| 兴城市| 来凤县| 汝城县| 麦盖提县| 凌云县| 正阳县| 中阳县| 邵阳县| 渝中区| 广灵县| 柳江县| 汕头市| 五莲县| 顺义区| 桑植县| 扶绥县| 翼城县| 德保县| 迁西县| 朝阳县| 老河口市| 南康市| 嘉黎县| 和龙市| 文昌市| 那曲县| 湘西| 双牌县| 湟中县| 洛川县| 萍乡市| 华坪县| 漳州市|