国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 在java中使用SPI創建可擴展的應用程序操作

在java中使用SPI創建可擴展的應用程序操作

2020-09-15 01:07flydean程序那些事 Java教程

這篇文章主要介紹了在java中使用SPI創建可擴展的應用程序操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

簡介

什么是可擴展應用程序呢?可擴展的意思是不需要修改原始代碼,就可以擴展應用程序的功能。我們將應用程序做成插件或者模塊。

這樣可以在不修改原應用的基礎上,對系統功能進行升級或者定制化。

本文將會向大家介紹如何通過java中的SPI機制實現這種可擴展的應用程序。

SPI簡介

SPI的全稱是Java Service Provider Interface。是java提供的一種服務發現的機制。

通過遵循相應的規則編寫應用程序之后,就可以使用ServiceLoader來加載相應的服務了。

SPI的實現主要分為4個部分:

Service Provider Interface: SPI是一個interface或者是抽象類,其中定義了我們需要擴展實現的功能。

Service Providers:這是SPI的具體實現,提供了具體的實現功能

SPI Configuration File:SPI的配置文件,通過在配置文件我們來配置相關的SPI發現信息。

ServiceLoader: ServiceLoader是用來加載和發現服務的java類,并提供了很多有用的方法。

SPI的普通java實現

講完SPI的定義,大家可能還是不清楚SPI到底是做什么的,又該怎么使用它。

不用急,我們下面通過一個例子來說明。

首先創建一個module:SPI-service,里面主要定義了一個ModuleService接口:

public interface ModuleService {

}

在java中使用SPI創建可擴展的應用程序操作

然后再分別創建兩個module,作為ModuleService的實現:

public class ModuleServiceA implements ModuleService {

  public ModuleService getModuleService(){
    return new ModuleServiceA();
  }
}
public class ModuleServiceB implements ModuleService {

  public ModuleService getModuleService(){
    return new ModuleServiceB();
  }
}

接著分別在兩個module中創建META-INF/services文件夾,并且在里面創建兩個以 Service Provider Interface限定名為名字的文件,這里文件名是:com.flydean.base.service.ModuleService,文件里面存放的是SPI的具體實現類:

com.flydean.base.servicea.ModuleServiceA

com.flydean.base.serviceb.ModuleServiceB

在java中使用SPI創建可擴展的應用程序操作

在java中使用SPI創建可擴展的應用程序操作

最后,我們需要創建一個使用SPI的類:

public class ModuleController {

  public static void main(String[] args) {
    List<ModuleService> moduleServices = ServiceLoader
        .load(ModuleService.class).stream()
        .map(ServiceLoader.Provider::get)
        .collect(toList());
    log.info("{}", moduleServices);
  }
}

在java中使用SPI創建可擴展的應用程序操作

為了更好的展示擴展應用的實際使用,我們分別創建4個模塊。在實際應用中,只需要將這些jar包加入應用程序的classpath即可。

運行看下輸出結果:

[com.flydean.base.servicea.ModuleServiceA@16f65612,

com.flydean.base.serviceb.ModuleServiceB@311d617d]

從結果看到,我們獲得了兩個ModuleService。證明系統擴展成功。

SPI在JPMS模塊化系統下的實現

上面我們講的是基本的操作,考慮一下,如果是在JDK9之后,引入了JPMS模塊化系統之后,應該怎么使用SPI呢?

代碼肯定是一樣,我們需要修改的是SPI配置文件。

如果在JPMS中,我們就不需要使用META-INF/services了,我們只需要創建相應的module-info.java文件即可。

先看下SPI模塊的module-info.java文件:

module com.flydean.service {
  exports com.flydean.service;
}

這個模塊我們對外暴露了service package,供其他模塊調用。

在java中使用SPI創建可擴展的應用程序操作

接下來是SPI的實現模塊:

module com.flydean.servicea {
  requires com.flydean.service;
  provides com.flydean.service.ModuleService with com.flydean.servicea.ModuleServiceA;
  exports com.flydean.servicea;
}

這里我們使用了provides命令,定義了兩個類的關聯關系。

在java中使用SPI創建可擴展的應用程序操作

最后是調用的模塊:

module com.flydean.controller {
  uses com.flydean.service.ModuleService;
  requires com.flydean.service;
  requires lombok;
  requires slf4j.api;
}

這里我們使用uses關鍵詞來引用ModuleService。

在java中使用SPI創建可擴展的應用程序操作

總結

本文介紹了SPI在模塊化和非模塊化系統中的應用。

本文中的例子:learn-java-base-9-to-20

補充知識:Java中SPI機制詳解

本文通過探析JDK提供的,在開源項目中比較常用的Java SPI機制,希望給大家在實際開發實踐、學習開源項目提供參考。

一、 SPI是什么

SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。

整體機制圖如下:

在java中使用SPI創建可擴展的應用程序操作

Java SPI 實際上是“基于接口的編程+策略模式+配置文件”組合實現的動態加載機制。

系統設計的各個抽象,往往有很多不同的實現方案,在面向的對象的設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。

Java SPI就是提供這樣的一個機制:為某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。所以SPI的核心思想就是解耦。

二、使用場景

概括地說,適用于:調用者根據實際使用需要,啟用、擴展、或者替換框架的實現策略。

比較常見的例子:

數據庫驅動加載接口實現類的加載

JDBC加載不同類型數據庫的驅動

日志門面接口實現類加載

SLF4J加載不同提供商的日志實現類

Spring

Spring中大量使用了SPI,比如:對servlet3.0規范

對ServletContainerInitializer的實現、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等

Dubbo

Dubbo中也大量使用SPI的方式實現框架的擴展, 不過它對Java提供的原生SPI做了封裝,允許用戶擴展實現Filter接口

三、使用介紹

要使用Java SPI,需要遵循如下約定:

當服務提供者提供了接口的一種具體實現后,在jar包的META-INF/services目錄下創建一個以“接口全限定名”為命名的文件,內容為實現類的全限定名;

接口實現類所在的jar包放在主程序的classpath中;

主程序通過java.util.ServiceLoder動態裝載實現模塊,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;

SPI的實現類必須攜帶一個不帶參數的構造方法;

四、示例代

步驟1、定義一組接口 (假設是org.foo.demo.IShout),并寫出接口的一個或多個實現,(假設是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。

public interface IShout {
  void shout();
}
public class Cat implements IShout {
  @Override
  public void shout() {
    System.out.println("miao miao");
  }
}
public class Dog implements IShout {
  @Override
  public void shout() {
    System.out.println("wang wang");
  }
}

步驟2、在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 (org.foo.demo.IShout文件),內容是要應用的實現類(這里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一個類)。

文件位置

- src

-main

-resources

- META-INF

- services

- org.foo.demo.IShout

文件內容

org.foo.demo.animal.Dog

org.foo.demo.animal.Cat

步驟3、使用 ServiceLoader 來加載配置文件中指定的實現。

public class SPIMain {
  public static void main(String[] args) {
    ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
    for (IShout s : shouts) {
      s.shout();
    }
  }
}

代碼輸出:

wang wang

miao miao

五、原理解析

首先看ServiceLoader類的簽名類的成員變量:

public final class ServiceLoader<S> implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";

  // 代表被加載的類或者接口
  private final Class<S> service;

  // 用于定位,加載和實例化providers的類加載器
  private final ClassLoader loader;

  // 創建ServiceLoader時采用的訪問控制上下文
  private final AccessControlContext acc;

  // 緩存providers,按實例化的順序排列
  private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

  // 懶查找迭代器
  private LazyIterator lookupIterator; 
  ......
}

參考具體ServiceLoader具體源碼,代碼量不多,加上注釋一共587行,梳理了一下,實現的流程如下:

1、 應用程序調用ServiceLoader.load方法

ServiceLoader.load方法內先創建一個新的ServiceLoader,并實例化該類中的成員變量,包括:

loader(ClassLoader類型,類加載器)

acc(AccessControlContext類型,訪問控制器)

providers(LinkedHashMap<String,S>類型,用于緩存加載成功的類)

lookupIterator(實現迭代器功能)

2、應用程序通過迭代器接口獲取對象實例,ServiceLoader先判斷成員變量providers對象中(LinkedHashMap<String,S>類型)是否有緩存實例對象,如果有緩存,直接返回。如果沒有緩存,執行類的裝載,實現如下:

讀取META-INF/services/下的配置文件,獲得所有能被實例化的類的名稱,值得注意的是,ServiceLoader可以跨越jar包獲取META-INF下的配置文件,具體加載配置的實現代碼如下:

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);
    }

通過反射方法Class.forName()加載類對象,并用instance()方法將類實例化。

把實例化后的類緩存到providers對象中,(LinkedHashMap<String,S>類型),然后返回實例對象。

六、總結

優點:

使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。

缺點:

雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。如果你并不想用某些實現類,它也被加載并實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。

多個并發多線程使用ServiceLoader類的實例是不安全的。

以上這篇在java中使用SPI創建可擴展的應用程序操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持WEB開發者。

原文鏈接:https://blog.csdn.net/superfjj/article/details/108166134

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 日日干狠狠干 | 日韩午夜在线视频 | 中文在线一区二区 | 不用播放器的免费av | 日日摸夜夜添夜夜添精品视频 | 伊人久久综合 | 日韩欧美在线视频 | 免费观看污污视频 | 中文字幕乱码一区二区三区 | 日韩欧美在线观看 | 中文字幕国产视频 | ts人妖另类精品视频系列 | 国产一区二区影院 | 亚洲国产高清在线 | 久久a毛片 | 3d动漫精品一区二区三区 | 国产精品成av人在线视午夜片 | 日韩精品色 | 影音先锋国产精品 | 日韩一区二区视频在线 | 91精品国产一区二区三区 | 91久久久久久久久 | 日韩不卡一区二区三区 | 这里只有精品在线视频观看 | 国产毛片网站 | 黄色一级毛片网站 | 国产黄色av| 91精品啪aⅴ在线观看国产 | 国产一区二区视频精品 | 精品国产成人在线 | 精品福利一区二区三区 | 国产精品欧美日韩在线观看 | 精品久久久精品 | 国产亚洲一区二区三区在线观看 | 精品国产91乱码一区二区三区 | 综合99| 亚洲精品日本 | 日韩超级大片免费看国产国产播放器 | 国产一区免费视频 | 免费黄色在线观看 | 国产一区二区三区视频在线观看 |