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

淺析Java中的SPI原理

 更新時間:2022年09月20日 08:36:19   作者:一只烤鴨朝北走  
SPI:由調(diào)用方制定接口標準,實現(xiàn)方來針對接口提供不同的實現(xiàn),SPI其實就是"為接口查找實現(xiàn)"的一種服務(wù)發(fā)現(xiàn)機制。本文將淺談一下SPI機制的原理,需要的可以參考一下

在面向?qū)ο蟮某绦蛟O(shè)計中,模塊之間交互采用接口編程,通常情況下調(diào)用方不需要知道被調(diào)用方的內(nèi)部實現(xiàn)細節(jié),因為一旦涉及到了具體實現(xiàn),如果需要換一種實現(xiàn)就需要修改代碼,這違反了程序設(shè)計的"開閉原則"。所以我們一般有兩種選擇:一種是使用API(Application Programming Interface),另一種是SPI(Service Provider Interface),API通常被應(yīng)用程序開發(fā)人員使用,而SPI通常被框架擴展人員使用。

在進入下面學(xué)習(xí)之前,我們先來再加深一下API和SPI這兩個的印象:

API:由實現(xiàn)方制定接口標準并完成對接口的不同實現(xiàn),這種模式服務(wù)接口從概念上更接近于實現(xiàn)方;

SPI:由調(diào)用方制定接口標準,實現(xiàn)方來針對接口提供不同的實現(xiàn);從前半句話我們來看,SPI其實就是"為接口查找實現(xiàn)"的一種服務(wù)發(fā)現(xiàn)機制;這種模式,服務(wù)接口組織上位于調(diào)用方所在的包中,實現(xiàn)位于獨立的包中。

API和SPI簡略圖示:

  

看完上面的簡單圖示,相信大家對API和SPI的區(qū)別有了一個大致的了解,現(xiàn)在我們使用SPI機制來實現(xiàn)我們一個簡單的日志框架:

第一步,創(chuàng)建一個maven項目命名為spi-interface,定義一個SPI對外服務(wù)接口,用來后續(xù)提供給調(diào)用者使用;

package cn.com.wwh;
/**
 * 
 * @FileName Logger.java
 * @version:1.0
 * @Description: 服務(wù)提供者接口
 * @author: wwh
 * @date: 2022年9月19日 上午10:31:53
 */
public interface Logger {
    
    /**
     * 
     * @Description:(功能描述)
     * @param msg
     */
    public void info(String msg);
    
    /**
     * 
     * @Description:(功能描述)
     * @param msg
     */
    public void debug(String msg);
}
package cn.com.wwh;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;

/**
 * 
 * @FileName LoggerService.java
 * @version:1.0
 * @Description: 為服務(wù)的調(diào)用者提供特定的功能,是SPI的核心功能
 * @author: wwh
 * @date: 2022年9月19日 上午10:33:30
 */
public class LoggerService {

    private static final LoggerService INSTANCE = new LoggerService();

    private final Logger logger;                          

    private final List<Logger> loggers = new ArrayList<>();

    private LoggerService() {
     //ServiceLoader是實現(xiàn)SPI的核心類
        ServiceLoader<Logger> sl = ServiceLoader.load(Logger.class);
        Iterator<Logger> it = sl.iterator();
        while (it.hasNext()) {
            loggers.add(it.next());
        }

        if (!loggers.isEmpty()) {
            logger = loggers.get(0);
        } else {
            logger = null;
        }
    }

    /**
     * @Description:(功能描述)
     * @return
     */
    public static LoggerService getLoggerService() {
        return INSTANCE;
    }

    /**
     * 
     * @Description:(功能描述)
     * @param msg
     */
    public void info(String msg) {
        if (logger == null) {
            System.err.println("在info方法中沒有找到Logger的實現(xiàn)類...");
        } else {
            logger.info(msg);
        }
    }

    /**
     * 
     * @Description:(功能描述)
     * @param msg
     */
    public void debug(String msg) {
        if (logger == null) {
            System.err.println("在debug方法中沒有找到Logger的實現(xiàn)類...");
        } else {
            logger.info(msg);
        }
    }
}

將上面這個這個項目打成spi-interface.jar包。

第二步,新建一個maven項目并導(dǎo)入第一步中打出來的spi-interface.jar包,這個項目用來提供服務(wù)的實現(xiàn),定義一個類,實現(xiàn)第一步中定義的cn.com.wwh.Logger接口,示例代碼如下:

package cn.com.wwh;

import cn.com.pep.Logger;

/**
 * 
 * @FileName Logback.java
 * @version:1.0
 * @Description: 服務(wù)接口的實現(xiàn)類
 * @author: wwh
 * @date: 2022年9月19日 上午10:50:31
 */
public class Logback implements Logger {

    @Override
    public void debug(String msg) {
        System.err.println("調(diào)用Logback的debug方法,輸出的日志為:" + msg);
    }

    @Override
    public void info(String msg) {
        System.err.println("調(diào)用Logback的info方法,輸出的日志為:" + msg);
    }

}

同時在當前項目的classpath路徑下建立META-INF/services/文件夾(至于為什么這么建立目錄,我們一會兒再解釋),并且新建一個名稱為cn.com.wwh.Logger內(nèi)容為cn.com.wwh.Logback的文件,這一步是關(guān)鍵(具體作用后面再詳細說明),然后將上面第二步這個這個項目打成spi-provider.jar包,供給之后使用,我目前使用的開發(fā)工具是Eclipse,目錄結(jié)構(gòu)如下圖所示:

第三步,編寫測試類,新建一個maven項目,命名為spi-test,導(dǎo)入前面兩個步驟打的spi-interface.jar和spi-provider.jar這兩個jar包,并編寫測試代碼,示例如下:

package cn.com.wwh;

import cn.com.pep.LoggerService;

/**
 * 
 * @FileName SpiTest.java
 * @version:1.0
 * @Description: 
 * @author: wwh
 * @date: 2022年9月19日 上午10:56:31
 */
public class SpiTest {
    
    public static void main(String[] args) {
        LoggerService logger = LoggerService.getLoggerService();
        logger.info("我是中國人");
        logger.debug("白菜多少錢一斤");
    }
}

有了SPI我們可以將服務(wù)和服務(wù)提供者輕松地解耦,假如將來的某一天我們需要將日志保存到數(shù)據(jù)庫,或者通過網(wǎng)絡(luò)發(fā)送,我們直接只需要替換針對服務(wù)接口的實現(xiàn)類即可,別的地方都不用修改,這更符合程序設(shè)計中的“開閉原則”。

SPI的大致原理是:應(yīng)用啟動的時候,掃描classpath下面的所有jar包,將jar包下的/META-INF/services/目錄下的文件加載到內(nèi)存中,進行一系列的解析(文件的名稱是spi接口的全路徑名稱,文件內(nèi)容應(yīng)該是spi接口實現(xiàn)類的全路徑名,可以用多個實現(xiàn)類,在文件中換行保存),之后判斷當前類和當前接口是否是同一類型?結(jié)果為true,則通過反射生成指定類的實例對象,保存到一個map集合中,可以通過遍歷或者迭代的方式拿出來使用。

SPI實質(zhì)就是一個加載服務(wù)實現(xiàn)的工具,核心類是ServiceLoader,其實了解了SPI的原理,我們再接著探究JDK中的源碼就沒有那么費力了,下面我們開始源碼分析吧。

ServiceLoader類是定義在java.util包下的,使用final定義禁止子類繼承和修改,實現(xiàn)了Iterable接口,使得可以通過迭代或者遍歷的方式獲取SPI接口的不同實現(xiàn)。

從上面的我們所舉的例子中,我們知道SPI的入口是ServiceLoader.load(Class<S> service)方法,我們來看看它都干了什么?   

上面的這4步總的來說,就是使用指定的類型和當前線程綁定的classLoader實例化了一個LazyIterator對象賦值給lookupIterator這個引用,并且清除了原來providers列表中緩存的服務(wù)的實現(xiàn)。接下來我們調(diào)用了ServiceLoader實例的iterator()方法獲取了一個迭代器,代碼如下:

public Iterator<S> iterator() {
        //通過匿名內(nèi)部類方式提供了一個迭代器
        return new Iterator<S>() {
            //獲取緩存的服務(wù)實現(xiàn)者的迭代器
            Iterator<Map.Entry<String, S>> knownProviders = providers.entrySet().iterator();

            //判斷迭代器中是否還有元素
            public boolean hasNext() {
                //緩存的服務(wù)實現(xiàn)者的迭代器中已經(jīng)沒有元素了
                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();
            }
        };
   }

我們接著調(diào)用上步獲取的迭代器it的hasNext()方法,因為我們在ServiceLoader.load()過程中其實是清除了providers列表中的緩存服務(wù)實現(xiàn)的,所以其實調(diào)用的是lookupIterator.hasNext()方法,如下:

public boolean hasNext() {
        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);
            }
        }
        //遍歷配置文件內(nèi)容
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());//配置文件內(nèi)容解析
        }
        nextName = pending.next();//獲取服務(wù)實現(xiàn)類的全路徑名
        return true;
    }

假如上部判斷為true,緊接著我們又調(diào)用了迭代器it的next()方式,同理也調(diào)用的是lookupIterator.next()方法,源碼如下:

public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;//文件中保存的服務(wù)接口實現(xiàn)類的全路徑名
        nextName = null;
        Class<?> c = null;
        try {
            //獲取全限定名的Class對象
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
        }
            //判斷實現(xiàn)類和服務(wù)接口是否是同一類型
        if (!service.isAssignableFrom(c)) {
            fail(service, "Provider " + cn + " not a subtype");
        }
        try {
            //通過反射生成服務(wù)接口的實現(xiàn)類,并判斷這個實例是否是接口的實現(xiàn)
            S p = service.cast(c.newInstance());
            //將服務(wù)接口的實現(xiàn)緩存起來,并返回
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service, "Provider " + cn + " could not be instantiated", x);
        }
        throw new Error(); // This cannot happen
    }

其實spi實現(xiàn)的主要流程是:掃描classpath路徑下的所有jar包下的/META-INF/services/目錄(即我們需要將服務(wù)接口的具體實現(xiàn)類暴露在這個目錄下,之前我們提到需要在實現(xiàn)類的classpath下面建立一個/META-INF/services/文件夾就是這個原因。),找到對應(yīng)的文件,讀取這個文件名找到對應(yīng)的SPI接口,然后通過InputStream流將文件內(nèi)容讀出來,獲取到實現(xiàn)類的全路徑名,并得到這個全路徑名所表示的Class對象,判斷其與服務(wù)接口是否是同一類型,然后通過反射生成服務(wù)接口的實現(xiàn),并保存在providers列表中,供給后續(xù)的使用。

SPI這種設(shè)計方式為我們的應(yīng)用擴展提供了極大的便利,但是它的短板也是顯而易見的,Java SPI 在查找擴展實現(xiàn)類的時候遍歷 SPI 的配置文件并且將實現(xiàn)類全部實例化,假設(shè)一個實現(xiàn)類初始化過程比較消耗資源且耗時,但是你的代碼里面又用不上它,這就產(chǎn)生了資源的浪費。所以說 Java SPI 無法按需加載實現(xiàn)類。

另外,SPI 機制在很多框架中都有應(yīng)用:slf4j日志框架、Spring 框架的基本原理也是類似的反射。還有 Dubbo 框架提供同樣的 SPI 擴展機制,只不過 Dubbo 和 spring 框架中的 SPI 機制具體實現(xiàn)方式跟咱們今天學(xué)得這個有些細微的區(qū)別(Dubbo可以實現(xiàn)按需加載實現(xiàn)類),不過整體的原理都是一致的,我們今天先對SPI有個簡單的了解,相信有了今天的基礎(chǔ)理解剩下的那幾個也不是什么難事。

好了,今天就到這兒了,文章中有說的不對的地方還請各位大佬批評指正,一起學(xué)習(xí),共同進步,謝謝。

到此這篇關(guān)于淺析Java中的SPI原理的文章就介紹到這了,更多相關(guān)Java SPI原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中對象轉(zhuǎn)json字符串的三種常用方式

    java中對象轉(zhuǎn)json字符串的三種常用方式

    本文主要介紹了java中對象轉(zhuǎn)json字符串的三種常用方式,包含Jackson庫,Gson庫和Hutool工具類這三種,具有一定的參考價值,感興趣的可以了解一下
    2024-06-06
  • springSecurity用戶認證和授權(quán)的實現(xiàn)

    springSecurity用戶認證和授權(quán)的實現(xiàn)

    Spring?Security?是一個開源的安全框架,提供了基于權(quán)限的訪問控制、身份認證的功能,本文主要介紹了springSecurity用戶認證和授權(quán),具有一定參考價值,感興趣的可以了解一下
    2024-04-04
  • Spring中的事務(wù)隔離級別和傳播行為

    Spring中的事務(wù)隔離級別和傳播行為

    這篇文章主要介紹了Spring中的事務(wù)隔離級別和傳播行為,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • java中的Consumer、Supply如何實現(xiàn)多參數(shù)?

    java中的Consumer、Supply如何實現(xiàn)多參數(shù)?

    Java的Consumer接口只能接受一個參數(shù),但可以通過自定義接口、使用Tuple或嵌套結(jié)構(gòu)來實現(xiàn)對多個參數(shù)的處理,對于Supplier接口,它不能接受參數(shù),但可以通過自定義BiSupplier、結(jié)合Function或封裝參數(shù)為對象來實現(xiàn)對兩個參數(shù)并返回一個值的功能
    2024-11-11
  • Spring中的SpringApplicationRunListener詳細解析

    Spring中的SpringApplicationRunListener詳細解析

    這篇文章主要介紹了Spring中的SpringApplicationRunListener詳細解析,SpringApplicationRunListener是一個監(jiān)聽SpringApplication中run方法的接口,在項目啟動過程的各個階段進行事件的發(fā)布,需要的朋友可以參考下
    2023-11-11
  • Springboot集成ProtoBuf的實例

    Springboot集成ProtoBuf的實例

    這篇文章主要介紹了Springboot集成ProtoBuf的實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java中thread線程start和run的區(qū)別

    java中thread線程start和run的區(qū)別

    這篇文章主要介紹了java中thread線程start和run的區(qū)別,run()是Runnable接口中定義的一個方法,是為了讓客戶程序員在這個方法里寫自己的功能代碼的。直接調(diào)用和普通的類調(diào)用自己的成員方法是沒有任何區(qū)別的
    2014-03-03
  • Java基于Calendar類輸出指定年份和月份的日歷代碼實例

    Java基于Calendar類輸出指定年份和月份的日歷代碼實例

    這篇文章主要介紹了Java 使用Calendar類輸出指定年份和月份的日歷,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • Java+Springboot搭建一個在線網(wǎng)盤文件分享系統(tǒng)

    Java+Springboot搭建一個在線網(wǎng)盤文件分享系統(tǒng)

    本主要介紹了通過springboot+freemark+jpa+MySQL實現(xiàn)的在線網(wǎng)盤文件分享系統(tǒng),其功能跟百度網(wǎng)盤非常類似,可以實現(xiàn)文件的上傳、移動、復(fù)制、下載等,需要的可以參考一下
    2021-11-11
  • springboot如何通過session實現(xiàn)單點登入詳解

    springboot如何通過session實現(xiàn)單點登入詳解

    單點登錄(SSO)的定義是在多個應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng),下面這篇文章主要給大家介紹了關(guān)于springboot如何通過session實現(xiàn)單點登入的相關(guān)資料,需要的朋友可以參考下
    2021-12-12

最新評論

元阳县| 盐池县| 利津县| 新巴尔虎左旗| 东方市| 尚志市| 翼城县| 巴塘县| 广安市| 浦县| 左云县| 建水县| 永城市| 临洮县| 奈曼旗| 武宁县| 荔波县| 花莲县| 迁西县| 鹤岗市| 翁源县| 潼南县| 保山市| 江达县| 巢湖市| 化州市| 宾阳县| 碌曲县| 陕西省| 临桂县| 自治县| 阿拉尔市| 武平县| 巴楚县| 涿鹿县| 桐乡市| 巴彦县| 满城县| 东宁县| 清苑县| 德州市|