欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解java實(shí)踐SPI機(jī)制及淺析源碼

 更新時(shí)間:2020年07月23日 10:29:19   作者:溪~源  
這篇文章主要介紹了詳解java實(shí)踐SPI機(jī)制及淺析源碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

1.概念

正式步入今天的核心內(nèi)容之前,溪源先給大家介紹一下關(guān)于SPI機(jī)制的相關(guān)概念,最后會(huì)提供實(shí)踐源代碼。

SPI即Service Provider Interface,屬于JDK內(nèi)置的一種動(dòng)態(tài)的服務(wù)提供發(fā)現(xiàn)機(jī)制,可以理解為運(yùn)行時(shí)動(dòng)態(tài)加載接口的實(shí)現(xiàn)類。更甚至,大家可以將SPI機(jī)制與設(shè)計(jì)模式中的策略模式建立聯(lián)系。

SPI機(jī)制:

從上圖中理解SPI機(jī)制:標(biāo)準(zhǔn)化接口+策略模式+配置文件;

SPI機(jī)制核心思想:系統(tǒng)設(shè)計(jì)的各個(gè)抽象,往往有很多不同的實(shí)現(xiàn)方案,在面向的對(duì)象的設(shè)計(jì)里,一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則,如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制

使用場(chǎng)景:

  • 1.數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載:面對(duì)不同廠商的數(shù)據(jù)庫(kù),JDBC需要加載不同類型的數(shù)據(jù)庫(kù)驅(qū)動(dòng);
  • 2.日志接口實(shí)現(xiàn):SLF4J加載不同日志實(shí)現(xiàn)類;
  • 3.溪源在實(shí)際開發(fā)中也使用了SPI機(jī)制:面對(duì)不同儀器平臺(tái)的結(jié)果文件上傳需要解析具體的結(jié)果,文件不同,解析邏輯不同,因此采用SPI機(jī)制能夠解耦和降低維護(hù)成本;

SPI機(jī)制使用約定:

從上面的圖中,我們可以清晰的知道SPI的三部分:接口+實(shí)現(xiàn)類+配置文件;因此,項(xiàng)目中若要利用SPI機(jī)制,則需要遵循以下約定:

  • 當(dāng)服務(wù)提供者提供了接口的一種具體實(shí)現(xiàn)后,在jar包的META-INF/services目錄下創(chuàng)建一個(gè)以“接口全限定名”為命名的文件,內(nèi)容為實(shí)現(xiàn)類的全限定名。
  • 主程序通過(guò)java.util.ServiceLoder動(dòng)態(tài)裝載實(shí)現(xiàn)模塊,它通過(guò)掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名,把類加載到JVM;

注意:除SPI,我還發(fā)布了最新Java架構(gòu)項(xiàng)目實(shí)戰(zhàn)教程+大廠面試題庫(kù), 點(diǎn)擊此處免費(fèi)獲取,小白勿進(jìn)!

2.實(shí)踐

整體包結(jié)構(gòu)如圖:

新建標(biāo)準(zhǔn)化接口:

public interface SayService {
  void say(String word);
}

建立兩個(gè)實(shí)現(xiàn)類

@Service
public class ASayServiceImpl implements SayService {
  @Override
  public void say(String word) {
    System.out.println(word + " A say: I am a boy");
  }
}


@Service
public class BSayServiceImpl implements SayService {
  @Override
  public void say(String word) {
    System.out.println(word + " B say: I am a girl");
  }
}

新建META-INF/services目錄和配置文件(以接口全限定名)

配置文件內(nèi)容為實(shí)現(xiàn)類全限定名

com.qxy.spi.impl.ASayServiceImpl
com.qxy.spi.impl.BSayServiceImpl

單測(cè)

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpiTest {

  static ServiceLoader<SayService> services = ServiceLoader.load(SayService.class);

  @Test
  public void test1() {
    for (SayService sayService : services) {
      sayService.say("Hello");
    }
  }

}

結(jié)果

Hello A say: I am a boy
Hello B say: I am a girl

3.源碼

源碼主要加載流程如下:

應(yīng)用程序調(diào)用ServiceLoader.load方法 ServiceLoader.load方法內(nèi)先創(chuàng)建一個(gè)新的ServiceLoader,并實(shí)例化該類中的成員變量;

  • loader(ClassLoader類型,類加載器)
  • acc(AccessControlContext類型,訪問(wèn)控制器)
  • providers(LinkedHashMap<String,S>類型,用于緩存加載成功的類)
  • lookupIterator(實(shí)現(xiàn)迭代器功能)

應(yīng)用程序通過(guò)迭代器接口獲取對(duì)象實(shí)例 ServiceLoader先判斷成員變量providers對(duì)象中(LinkedHashMap<String,S>類型)是否有緩存實(shí)例對(duì)象,如果有緩存,直接返回。如果沒(méi)有緩存,執(zhí)行類的裝載。

  • 讀取META-INF/services/下的配置文件,獲得所有能被實(shí)例化的類的名稱,值得注意的是,ServiceLoader可以跨越j(luò)ar包獲取META-INF下的配置文件;
  • 通過(guò)反射方法Class.forName()加載類對(duì)象,并用instance()方法將類實(shí)例化。
  • 把實(shí)例化后的類緩存到providers對(duì)象中,(LinkedHashMap<String,S>類型) 然后返回實(shí)例對(duì)象。
public final class ServiceLoader<S>
  implements Iterable<S>
{
  // 加載具體實(shí)現(xiàn)類信息的前綴
  private static final String PREFIX = "META-INF/services/";

  // 需要加載的接口
  // The class or interface representing the service being loaded
  private final Class<S> service;

  // 用于加載的類加載器
  // The class loader used to locate, load, and instantiate providers
  private final ClassLoader loader;

  // 創(chuàng)建ServiceLoader時(shí)采用的訪問(wèn)控制上下文
  // The access control context taken when the ServiceLoader is created
  private final AccessControlContext acc;

  // 用于緩存已經(jīng)加載的接口實(shí)現(xiàn)類,其中key為實(shí)現(xiàn)類的完整類名
  // Cached providers, in instantiation order
  private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

  // 用于延遲加載接口的實(shí)現(xiàn)類
  // The current lazy-lookup iterator
  private LazyIterator lookupIterator;

  
  public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
  }

  private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
  }

  private static void fail(Class<?> service, String msg, Throwable cause)
    throws ServiceConfigurationError
  {
    throw new ServiceConfigurationError(service.getName() + ": " + msg,
                      cause);
  }

  private static void fail(Class<?> service, String msg)
    throws ServiceConfigurationError
  {
    throw new ServiceConfigurationError(service.getName() + ": " + msg);
  }

  private static void fail(Class<?> service, URL u, int line, String msg)
    throws ServiceConfigurationError
  {
    fail(service, u + ":" + line + ": " + msg);
  }

  // Parse a single line from the given configuration file, adding the name
  // on the line to the names list.
  //具體解析資源文件中的每一行內(nèi)容
  private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
             List<String> names)
    throws IOException, ServiceConfigurationError
  {
    String ln = r.readLine();
    if (ln == null) {
    	//-1表示解析完成
      return -1;
    }
    // 如果存在'#'字符,截取第一個(gè)'#'字符串之前的內(nèi)容,'#'字符之后的屬于注釋內(nèi)容
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
    	//不合法的標(biāo)識(shí):' '、'\t'
      if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
        fail(service, u, lc, "Illegal configuration-file syntax");
      int cp = ln.codePointAt(0);
      //判斷第一個(gè) char 是否一個(gè)合法的 Java 起始標(biāo)識(shí)符
      if (!Character.isJavaIdentifierStart(cp))
        fail(service, u, lc, "Illegal provider-class name: " + ln);
      	//判斷所有其他字符串是否屬于合法的Java標(biāo)識(shí)符
      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 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();
  }

  // Private inner class implementing fully-lazy provider lookup
  //
  private class LazyIterator
    implements Iterator<S>
  {

    Class<S> service;
    ClassLoader loader;
    // 加載資源的URL集合
	  Enumeration<URL> configs = null; 
	  // 需加載的實(shí)現(xiàn)類的全限定類名的集合
	  Iterator<String> pending = null;
	  // 下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名
	  String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
      this.service = service;
      this.loader = loader;
    }

    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
            configs = loader.getResources(fullName);
        } catch (IOException x) {
          fail(service, "Error locating configuration files", x);
        }
      }
      // 從資源中解析出需要加載的所有實(shí)現(xiàn)類的全限定名
      while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
          return false;
        }
        pending = parse(service, configs.nextElement());
      }
      //下一個(gè)需要加載的實(shí)現(xiàn)類全限定名
      nextName = pending.next();
      return true;
    }

    private S nextService() {
      if (!hasNextService())
        throw new NoSuchElementException();
      String cn = nextName;
      nextName = null;
      Class<?> c = null;
      try {
      //反射構(gòu)造Class實(shí)例
        c = Class.forName(cn, false, loader);
      } catch (ClassNotFoundException x) {
        fail(service,
           "Provider " + cn + " not found");
      }
      // 類型判斷,校驗(yàn)實(shí)現(xiàn)類必須與當(dāng)前加載的類/接口的關(guān)系是派生或相同,否則拋出異常終止
      if (!service.isAssignableFrom(c)) {
        fail(service,
           "Provider " + cn + " not a subtype");
      }
      try {
      	//強(qiáng)轉(zhuǎn)
        S p = service.cast(c.newInstance());
         // 實(shí)例完成,添加緩存,Key:實(shí)現(xiàn)類全限定類名,Value:實(shí)現(xiàn)類實(shí)例
        providers.put(cn, p);
        return p;
      } catch (Throwable x) {
        fail(service,
           "Provider " + cn + " could not be instantiated",
           x);
      }
      throw new Error();     // This cannot happen
    }

    public boolean hasNext() {
      if (acc == null) {
        return hasNextService();
      } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
          public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
      }
    }

    public S next() {
      if (acc == null) {
        return nextService();
      } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
          public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
      }
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }

  }

  
  public Iterator<S> iterator() {
    return new Iterator<S>() {

      Iterator<Map.Entry<String,S>> knownProviders
        = providers.entrySet().iterator();

      public boolean hasNext() {
        if (knownProviders.hasNext())
          return true;
        return lookupIterator.hasNext();
      }

      public S next() {
        if (knownProviders.hasNext())
          return knownProviders.next().getValue();
        return lookupIterator.next();
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }

    };
  }

  
  public static <S> ServiceLoader<S> load(Class<S> service,
                      ClassLoader loader)
  {
  // 返回ServiceLoader的實(shí)例
    return new ServiceLoader<>(service, loader);
  }

  
  public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
  
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
      prev = cl;
      cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
  }

  
  public String toString() {
    return "java.util.ServiceLoader[" + service.getName() + "]";
  }

}

4.總結(jié)

SPI機(jī)制在實(shí)際開發(fā)中使用得場(chǎng)景也有很多。特別是統(tǒng)一標(biāo)準(zhǔn)的不同廠商實(shí)現(xiàn),溪源也正是利用SPI機(jī)制(但略做改進(jìn),避免過(guò)多加載資源浪費(fèi))實(shí)現(xiàn)不同技術(shù)平臺(tái)的結(jié)果文件解析需求。

優(yōu)點(diǎn)

使用Java SPI機(jī)制的優(yōu)勢(shì)是實(shí)現(xiàn)解耦,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起。應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替換框架組件。

缺點(diǎn)

雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過(guò)遍歷全部獲取,也就是接口的實(shí)現(xiàn)類全部加載并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,它也被加載并實(shí)例化了,這就造成了浪費(fèi)。

源碼傳送門:SPI Service

到此這篇關(guān)于詳解java實(shí)踐SPI機(jī)制及淺析源碼的文章就介紹到這了,更多相關(guān)java SPI機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中鎖的分類與使用方法

    Java中鎖的分類與使用方法

    這篇文章主要給大家介紹了關(guān)于Java中鎖分類與使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 詳解Springboot對(duì)多線程的支持

    詳解Springboot對(duì)多線程的支持

    Spring是通過(guò)任務(wù)執(zhí)行器(TaskExecutor)來(lái)實(shí)現(xiàn)多線程和并發(fā)編程,使用ThreadPoolTaskExecutor來(lái)創(chuàng)建一個(gè)基于線城池的TaskExecutor。這篇文章給大家介紹Springboot對(duì)多線程的支持,感興趣的朋友一起看看吧
    2018-07-07
  • Java中獲取List中最后一個(gè)元素的三種方法

    Java中獲取List中最后一個(gè)元素的三種方法

    在Java編程中我們經(jīng)常需要獲取一個(gè)List集合中的最后一個(gè)元素,這篇文章主要給大家介紹了關(guān)于Java中獲取List中最后一個(gè)元素的三種方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • SpringBoot文件上傳功能的實(shí)現(xiàn)方法

    SpringBoot文件上傳功能的實(shí)現(xiàn)方法

    這篇文章主要介紹了SpringBoot文件上傳功能的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • web.xml?SpringBoot打包可執(zhí)行Jar運(yùn)行SpringMVC加載流程

    web.xml?SpringBoot打包可執(zhí)行Jar運(yùn)行SpringMVC加載流程

    這篇文章主要為大家介紹了web.xml?SpringBoot打包可執(zhí)行Jar運(yùn)行SpringMVC加載流程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • java多線程應(yīng)用實(shí)現(xiàn)方法

    java多線程應(yīng)用實(shí)現(xiàn)方法

    以前沒(méi)有寫筆記的習(xí)慣,現(xiàn)在慢慢的發(fā)現(xiàn)及時(shí)總結(jié)是多么的重要了,呵呵。雖然才大二,但是也快要畢業(yè)了,要加油
    2012-11-11
  • 使用Java實(shí)現(xiàn)接口攔截器來(lái)監(jiān)控接口的執(zhí)行情況

    使用Java實(shí)現(xiàn)接口攔截器來(lái)監(jiān)控接口的執(zhí)行情況

    在排查問(wèn)題的時(shí)候,由于沒(méi)有對(duì)接口的執(zhí)行情況,以及入?yún)⑦M(jìn)行監(jiān)控,所以排查起問(wèn)題就特別費(fèi)勁,今天我們就一起來(lái)寫一個(gè)接口的攔截器來(lái)監(jiān)控接口的執(zhí)行情況吧
    2024-01-01
  • Windows下java、javaw、javaws以及jvm.dll等進(jìn)程的區(qū)別

    Windows下java、javaw、javaws以及jvm.dll等進(jìn)程的區(qū)別

    這篇文章主要介紹了Windows下java、javaw、javaws以及jvm.dll等進(jìn)程的區(qū)別,本文分別講解了它們的作用并給出代碼實(shí)例,最后做出了區(qū)別總結(jié),需要的朋友可以參考下
    2015-03-03
  • Java實(shí)現(xiàn)二維碼功能的實(shí)例代碼

    Java實(shí)現(xiàn)二維碼功能的實(shí)例代碼

    今天這篇文章,主要是利用Java實(shí)現(xiàn)二維碼功能,本文思路清晰,需要的朋友參考下
    2017-02-02
  • java使用PageInfo的list通用分頁(yè)處理demo

    java使用PageInfo的list通用分頁(yè)處理demo

    這篇文章主要為大家介紹了java使用PageInfo的list通用分頁(yè)處理demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2023-12-12

最新評(píng)論