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

一文帶你了解Java中的SPI機(jī)制

 更新時(shí)間:2023年04月09日 08:51:58   作者:笨笨的二黃子  
SPI 全稱是 Service Provider Interface,是一種 JDK 內(nèi)置的動(dòng)態(tài)加載實(shí)現(xiàn)擴(kuò)展點(diǎn)的機(jī)制,本文主要為大家介紹了SPI機(jī)制的原理與使用,需要的可以參考一下

1: SPI機(jī)制簡介

SPI 全稱是 Service Provider Interface,是一種 JDK 內(nèi)置的動(dòng)態(tài)加載實(shí)現(xiàn)擴(kuò)展點(diǎn)的機(jī)制,通過 SPI 技術(shù)我們可以動(dòng)態(tài)獲取接口的實(shí)現(xiàn)類,不用自己來創(chuàng)建。這個(gè)不是什么特別的技術(shù),只是 一種設(shè)計(jì)理念。

2: SPI原理

Java SPI 實(shí)際上是基于接口的編程+策略模式+配置文件組合實(shí)現(xiàn)的動(dòng)態(tài)加載機(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ī)制。

Java SPI就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。有點(diǎn)類似IOC的思想,就是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要。所以SPI的核心思想就是解耦。

3: 使用場景

調(diào)用者根據(jù)實(shí)際使用需要 啟用、擴(kuò)展、或者替換框架的實(shí)現(xiàn)策略

下面是一些使用了該機(jī)制的場景

  • JDBC驅(qū)動(dòng),加載不同數(shù)據(jù)庫的驅(qū)動(dòng)類
  • Spring中大量使用了SPI,比如:對(duì)servlet3.0規(guī)范對(duì)ServletContainerInitializer的實(shí)現(xiàn)、自動(dòng)類型轉(zhuǎn)換Type Conversion SPI(Converter SPI、Formatter SPI)等
  • Dubbo中也大量使用SPI的方式實(shí)現(xiàn)框架的擴(kuò)展, 不過它對(duì)Java提供的原生SPI做了封裝,允許用戶擴(kuò)展實(shí)現(xiàn)Filter接口
  • Tomcat 加載 META-INF/services下找需要加載的類
  • SpringBoot項(xiàng)目中 使用@SpringBootApplication注解時(shí),會(huì)開始自動(dòng)配置,而啟動(dòng)配置則會(huì)去掃描META-INF/spring.factories下的配置類

4: 源碼論證

4.1 應(yīng)用程序調(diào)用ServiceLoader.load方法

ServiceLoader.load方法內(nèi)先創(chuàng)建一個(gè)新的ServiceLoader,并實(shí)例化該類中的成員變量

    private static final String PREFIX = "META-INF/services/";


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

	/** 
     * 
     * 在調(diào)用該方法之后,迭代器方法的后續(xù)調(diào)用將延遲地從頭開始查找和實(shí)例化提供程序,就像新創(chuàng)建的加載程序所做的		  那樣
     */
   public void reload() {
        providers.clear(); //清除此加載程序的提供程序緩存,以便重新加載所有提供程序。
        lookupIterator = new LazyIterator(service, loader);
    }

	private class LazyIterator implements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;


        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //找到配置文件
                    String fullName = PREFIX + service.getName();
                    //加載配置文件中的內(nèi)容
                    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());
            }
            //獲取配置文件中內(nèi)容
            nextName = pending.next();
            return true;
        }
    }

		/** 
     	* 
     	*  通過反射 實(shí)例化配置文件中的具體實(shí)現(xiàn)類
    	 */
		private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                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
        }

5: 實(shí)戰(zhàn)

步驟1 新建以下類

public interface IService {

    /**
     * 獲取價(jià)格
     * @return
     */
    String getPrice();

    /**
     * 獲取規(guī)格信息
     * @return
     */
    String getSpecifications();
}
public class GoodServiceImpl implements IService {

    @Override
    public String getPrice() {
        return "2000.00元";
    }

    @Override
    public String getSpecifications() {
        return "200g/件";
    }
}
public class MedicalServiceImpl implements IService {

    @Override
    public String getPrice() {
        return "3022.12元";
    }

    @Override
    public String getSpecifications() {
        return "30粒/盒";
    }
}

步驟2、在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個(gè)以接口命名的文件 org.example.IService.txt 。內(nèi)容是要應(yīng)用的實(shí)現(xiàn)類,我這邊需要放入的數(shù)據(jù)如下

org.example.GoodServiceImpl
org.example.MedicalServiceImpl

步驟3、使用 ServiceLoader 來加載配置文件中指定的實(shí)現(xiàn)。

public class Main {
    public static void main(String[] args) {
        final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
        serviceLoader.forEach(service -> {
            System.out.println(service.getPrice() + "=" + service.getSpecifications());
        });
    }
}

輸出:

2000.00元=200g/件
3022.12元=30粒/盒

6: 優(yōu)缺點(diǎn)

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

解耦 使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起,應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替換框架組件。相比使用提供接口jar包,供第三方服務(wù)模塊實(shí)現(xiàn)接口的方式,SPI的方式使得源框架,不必關(guān)心接口的實(shí)現(xiàn)類的路徑

6.2 缺點(diǎn)

  • 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實(shí)現(xiàn)類全部加載并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,它也被加載并實(shí)例化了,這就造成了浪費(fèi)。獲取某個(gè)實(shí)現(xiàn)類的方式不夠靈活,只能通過Iterator形式獲取,不能根據(jù)某個(gè)參數(shù)來獲取對(duì)應(yīng)的實(shí)現(xiàn)類
  • 多個(gè)并發(fā)多線程使用ServiceLoader類的實(shí)例是不安全的

到此這篇關(guān)于一文帶你了解Java中的SPI機(jī)制的文章就介紹到這了,更多相關(guān)Java SPI機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 中校驗(yàn)時(shí)間格式的常見方法

    Java 中校驗(yàn)時(shí)間格式的常見方法

    在實(shí)際項(xiàng)目開發(fā)中,跟時(shí)間參數(shù)打交道是必不可少的,為了保證程序的安全性、健壯性,一般都會(huì)對(duì)參數(shù)進(jìn)行校驗(yàn),其他類型的參數(shù)校驗(yàn)很好實(shí)現(xiàn),那你知道時(shí)間參數(shù)的是怎么校驗(yàn)的嗎,下面給大家分享Java 中校驗(yàn)時(shí)間格式的方法,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • Redis內(nèi)存數(shù)據(jù)庫示例分析

    Redis內(nèi)存數(shù)據(jù)庫示例分析

    Redis本身的內(nèi)容比較復(fù)雜。如果你上來,你應(yīng)該研究一個(gè)細(xì)節(jié)點(diǎn),比如連接池和數(shù)據(jù)結(jié)構(gòu)。雖然可以直接了解某一點(diǎn)的詳細(xì)來源內(nèi)容,甚至盡快解決一些意外,但是容易淹沒在失眠的細(xì)節(jié)中,整體控制不了Redis
    2022-12-12
  • Java中上傳圖片壓縮處理的方法示例

    Java中上傳圖片壓縮處理的方法示例

    本篇文章主要介紹了Java中圖片壓縮處理的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-02-02
  • idea中maven項(xiàng)目模塊變成灰色原因及解決方案

    idea中maven項(xiàng)目模塊變成灰色原因及解決方案

    這篇文章主要介紹了idea中maven項(xiàng)目模塊變成灰色原因及解決方案,文中通過圖文結(jié)合的方式給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-03-03
  • Spring Security攔截器引起Java CORS跨域失敗的問題及解決

    Spring Security攔截器引起Java CORS跨域失敗的問題及解決

    這篇文章主要介紹了Spring Security攔截器引起Java CORS跨域失敗的問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • springboot代碼,注解配置獲取yml,properties文件的map即鍵值對(duì)

    springboot代碼,注解配置獲取yml,properties文件的map即鍵值對(duì)

    這篇文章主要介紹了springboot代碼,注解配置獲取yml,properties文件的map即鍵值對(duì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java判斷用戶輸入月份的季節(jié)

    Java判斷用戶輸入月份的季節(jié)

    這篇文章主要為大家詳細(xì)介紹了Java判斷用戶輸入月份的季節(jié),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • java獲取日期的方法

    java獲取日期的方法

    這篇文章介紹了java獲取日期的方法,有需要的朋友可以參考一下
    2013-10-10
  • 淺談java中靜態(tài)方法的重寫問題詳解

    淺談java中靜態(tài)方法的重寫問題詳解

    本篇文章是對(duì)java中靜態(tài)方法的重寫問題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • java編程scanner類用法示例

    java編程scanner類用法示例

    這篇文章主要介紹了java編程scanner類用法示例,涉及一個(gè)通過scanner類實(shí)現(xiàn)需要手動(dòng)輸入變量時(shí)進(jìn)行輸入的實(shí)例,然后分享了一個(gè)簡單的eclipse對(duì)Java代碼格式化的技巧,具有一定借鑒價(jià)值,需要的朋友可以參考。
    2017-11-11

最新評(píng)論