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

一文帶你掌握J(rèn)ava?SPI的原理和實(shí)踐

 更新時(shí)間:2023年05月30日 09:54:35   作者:守望時(shí)空33  
在Java中,我們經(jīng)常會(huì)提到面向接口編程,這樣減少了模塊之間的耦合,更加靈活,Java?SPI?(Service?Provider?Interface)就提供了這樣的機(jī)制,本文就來(lái)講講它的原理與具體使用吧

在Java中,我們經(jīng)常會(huì)提到面向接口編程,這樣減少了模塊之間的耦合,更加靈活。

在一個(gè)項(xiàng)目中我們也通常將接口和實(shí)現(xiàn)類放在一起,但是如果哪天我們要替換其它的實(shí)現(xiàn)類,或者是修改實(shí)現(xiàn)類,涉及到實(shí)現(xiàn)類的代碼也要相應(yīng)地修改。

能不能這樣:在調(diào)用服務(wù)的時(shí)候,我們只調(diào)用接口,不用關(guān)心實(shí)現(xiàn)類呢?無(wú)論我們?cè)趺辞袚Q實(shí)現(xiàn)類,調(diào)用接口的部分代碼都能正常運(yùn)行?

當(dāng)然是可以的,Java SPI (Service Provider Interface)就提供了這樣的機(jī)制。

Java SPI機(jī)制中,我們不再是手動(dòng)指定接口和實(shí)現(xiàn)類的關(guān)系,而是讓接口去尋找可用的實(shí)現(xiàn)類。

事實(shí)上,我們經(jīng)常使用的Spring框架、日志接口等等,都是使用了SPI機(jī)制實(shí)現(xiàn)了擴(kuò)展。

1、SPI和API

在說起SPI之前,我們還是先看一下API,API我們已經(jīng)很熟悉了,和SPI都可以被稱作接口。

只不過API功能的實(shí)現(xiàn),以及接口的定義全部是接口的實(shí)現(xiàn)者提供的,調(diào)用者只需要調(diào)用接口即可:

不過SPI就不一樣了,在SPI機(jī)制中,調(diào)用者仍然是調(diào)用接口,但是這個(gè)接口是獨(dú)立存在的,并且可以由不同的實(shí)現(xiàn)者實(shí)現(xiàn)

也就是說,這里接口只是一個(gè)標(biāo)準(zhǔn),并且提供接口的那一方并不一定回去實(shí)現(xiàn)接口,而是根據(jù)接口的定義,由更多的第三方實(shí)現(xiàn)。

這個(gè)接口可以由一個(gè)甚至是多個(gè)實(shí)現(xiàn)者去實(shí)現(xiàn)。也因此,調(diào)用者在調(diào)用接口時(shí),可能還需要指定一下使用哪個(gè)實(shí)現(xiàn)者的實(shí)現(xiàn)類。

實(shí)現(xiàn)者也叫做服務(wù)提供者。

事實(shí)上,我們?nèi)粘I钪薪?jīng)常使用的U盤也很類似SPI機(jī)制,U盤使用的是USB接口,USB接口僅僅是一個(gè)規(guī)范(接口),但是發(fā)明USB接口的公司并沒有去生產(chǎn)U盤,而是由不同的U盤廠商例如金士頓、閃迪(實(shí)現(xiàn)者)等等去根據(jù)這個(gè)規(guī)范生產(chǎn)U盤,然后我們就可以去選擇自己喜歡的牌子(選擇實(shí)現(xiàn)者)購(gòu)買U盤,不過平時(shí)無(wú)論使用什么牌子的U盤,我們只需要插入到電腦的USB接口(調(diào)用接口)即可使用,而不用關(guān)心不同的廠商是怎么實(shí)現(xiàn)USB接口的功能的。

可見,SPI機(jī)制將實(shí)現(xiàn)者和接口再次解耦合了,使得接口更加易于擴(kuò)展。

事實(shí)上,我們常常用的SLF4J就是一個(gè)Java的日志接口,但是它也僅僅是一個(gè)接口,所以被稱作門面。而它的實(shí)現(xiàn)有Logback、Log4j等等,并且在切換實(shí)現(xiàn)的時(shí)候,我們只需要修改一下依賴配置即可,代碼并不需要任何變動(dòng),因?yàn)榇a中也僅僅是調(diào)用了接口。

2、自己完成一個(gè)SPI

那么現(xiàn)在,我們也來(lái)以一個(gè)最簡(jiǎn)單的日志接口為例,實(shí)現(xiàn)自己的SPI。

(1) 定義SPI接口

先新建一個(gè)空的Maven項(xiàng)目log-interface,然后在里面創(chuàng)建一個(gè)日志接口,聲明日志接口具備的方法(功能):

package com.gitee.swsk33.loginterface.spi;
/**
 * 定義日志接口
 */
public interface Logger {
	/**
	 * INFO級(jí)別日志方法
	 *
	 * @param message 日志打印消息
	 */
	void info(String message);
	/**
	 * DEBUG級(jí)別日志方法
	 *
	 * @param message 日志打印消息
	 */
	void debug(String message);
}

這樣,我們便定義了這么一個(gè)日志接口,并聲明日志接口需要有infodebug這兩個(gè)日志功能。

然后就是編寫服務(wù)類,這個(gè)服務(wù)類是這里最為重要的地方,它的作用是掃描所有實(shí)現(xiàn)了Logger接口的實(shí)現(xiàn)類并加載進(jìn)來(lái),然后供調(diào)用者去調(diào)用

先看代碼:

package com.gitee.swsk33.loginterface.service;
import com.gitee.swsk33.loginterface.spi.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
/**
 * 服務(wù),用于加載所有服務(wù)使用者的實(shí)現(xiàn)類,以及供外部調(diào)用
 * 該類為一個(gè)單例
 */
public class LoggerService {
	/**
	 * 該類唯一單例
	 */
	private static final LoggerService LOGGER = new LoggerService();
	/**
	 * 默認(rèn)的Logger實(shí)現(xiàn)類
	 */
	private final Logger defaultLogger;
	/**
	 * 所有的Logger實(shí)現(xiàn)類列表
	 */
	private final List<Logger> allLoggers = new ArrayList<>();
	/**
	 * 私有化構(gòu)造器
	 */
	private LoggerService() {
		// 加載全部Logger接口的實(shí)現(xiàn)類
		ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
		// 將實(shí)現(xiàn)類放入我們的Logger實(shí)現(xiàn)類列表
		for (Logger logger : loader) {
			allLoggers.add(logger);
		}
		// 這里取出第一個(gè)作為默認(rèn)實(shí)現(xiàn)類
		if (!allLoggers.isEmpty()) {
			defaultLogger = allLoggers.get(0);
		} else {
			defaultLogger = null;
		}
		System.out.println("加載到" + allLoggers.size() + "個(gè)服務(wù)實(shí)現(xiàn)!");
	}
	/**
	 * 獲取該服務(wù)類的唯一單例
	 *
	 * @return 該服務(wù)類的唯一單例
	 */
	public static LoggerService getInstance() {
		return LOGGER;
	}
	/**
	 * 調(diào)用默認(rèn)的實(shí)現(xiàn)類的info日志打印方法
	 *
	 * @param message 消息
	 */
	public void info(String message) {
		if (defaultLogger == null) {
			System.err.println("沒有找到實(shí)現(xiàn)了Logger接口的類!");
			return;
		}
		defaultLogger.info(message);
	}
	/**
	 * 調(diào)用默認(rèn)的實(shí)現(xiàn)類的debug日志打印方法
	 *
	 * @param message 消息
	 */
	public void debug(String message) {
		if (defaultLogger == null) {
			System.err.println("沒有找到實(shí)現(xiàn)了Logger接口的類!");
			return;
		}
		defaultLogger.debug(message);
	}
}

首先這個(gè)類是一個(gè)單例的類,在構(gòu)造器中,我們使用ServiceLoader這個(gè)類來(lái)將實(shí)現(xiàn)了Logger接口的所有類都掃描進(jìn)來(lái),并存入我們的實(shí)現(xiàn)類列表,然后我們?nèi)〕隽斜碇械牡谝粋€(gè)作為默認(rèn)實(shí)現(xiàn)。

在下面我們定義了infodebug來(lái)完成對(duì)接口的默認(rèn)實(shí)現(xiàn)類的調(diào)用。

最后,在項(xiàng)目目錄下執(zhí)行mvn install命令將其安裝至本地Maven倉(cāng)庫(kù),以便后續(xù)服務(wù)提供者引入并實(shí)現(xiàn)。

(2) 完成一個(gè)接口的實(shí)現(xiàn)

現(xiàn)在再新建一個(gè)空的Maven項(xiàng)目logservice-one,并引入上面接口項(xiàng)目為依賴:

然后編寫實(shí)現(xiàn)類:

package com.gitee.swsk33.logserviceone.service;
import com.gitee.swsk33.loginterface.spi.Logger;
/**
 * Logger SPI的實(shí)現(xiàn)類
 */
public class LogOne implements Logger {
	@Override
	public void info(String s) {
		System.out.println("[LogOne INFO] " + s);
	}
	@Override
	public void debug(String s) {
		System.out.println("[LogOne DEBUG] " + s);
	}
}

然后在resources目錄下創(chuàng)建目錄META-INF/services,這個(gè)目錄中是用于聲明該服務(wù)實(shí)現(xiàn)中有哪些實(shí)現(xiàn)類實(shí)現(xiàn)了什么接口

在這個(gè)目錄下我們新建一個(gè)文件名為com.gitee.swsk33.loginterface.spi.Logger,文件中的內(nèi)容為:

com.gitee.swsk33.logserviceone.service.LogOne

可見,該目錄下文件名要實(shí)現(xiàn)的接口的全限定類名(包名 + 類名),而文件中內(nèi)容實(shí)現(xiàn)了該接口的實(shí)現(xiàn)類的全限定類名。

大家參考這里的文件名及其中的內(nèi)容,與我們上述的接口全限定類名、實(shí)現(xiàn)類全限定類名對(duì)比一下就知道了!

如果說這個(gè)項(xiàng)目中有多個(gè)類實(shí)現(xiàn)了Logger接口,那么我們都需要在文件中聲明,一行一個(gè)實(shí)現(xiàn)類的全限定類名。

最終整個(gè)項(xiàng)目結(jié)構(gòu)如下:

同樣地,最后記得在項(xiàng)目目錄下執(zhí)行mvn install命令將其安裝至本地Maven倉(cāng)庫(kù),以便調(diào)用者調(diào)用。

(3) 測(cè)試接口

這里再新建一個(gè)Maven空項(xiàng)目log-test,作為接口的調(diào)用者,在依賴中引入實(shí)現(xiàn)者

然后創(chuàng)建一個(gè)主類調(diào)用一下接口試試:

package com.gitee.swsk33.logtest;
import com.gitee.swsk33.loginterface.service.LoggerService;
public class Main {
	private static final LoggerService LOGGER = LoggerService.getInstance();
	public static void main(String[] args) {
		LOGGER.info("測(cè)試info消息");
		LOGGER.debug("測(cè)試debug消息");
	}
}

結(jié)果:

可見,我們成功地調(diào)用了Logger接口中的方法。

通常調(diào)用者的依賴中可能會(huì)同時(shí)引入SPI接口依賴和服務(wù)提供者(實(shí)現(xiàn))的依賴,這樣也沒問題,不過通常服務(wù)提供者本身就依賴于SPI接口,因此只引入服務(wù)提供者依賴,也會(huì)間接地引入SPI接口依賴,不影響我們調(diào)用SPI接口。

我們這里只有一個(gè)服務(wù)提供者logservice-one,如果說還有logservice-two等等多個(gè)服務(wù)提供者,我們只需要在依賴中更換一下即可,代碼完全不需要改變。

也可見調(diào)用者在調(diào)用接口的時(shí)候,只需要關(guān)注接口就行了,不需要關(guān)心實(shí)現(xiàn)類。

3、再看ServiceLoader

可見在SPI接口中,我們使用ServiceLoader完成了對(duì)所有實(shí)現(xiàn)了Logger接口的類的掃描和加載,那么具體的過程是什么樣的呢?

如果大家去查看這個(gè)類的源碼,可以發(fā)現(xiàn)它實(shí)現(xiàn)了Iterable接口,這也說明我們可以通過迭代的方式去完成多個(gè)實(shí)現(xiàn)類的切換。

然后在其源碼中,有這么一個(gè)常量定義:

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

這就說明,ServiceLoader會(huì)去掃描服務(wù)提供者的classpath路徑下的META-INF/services目錄,來(lái)掃描哪些類實(shí)現(xiàn)了指定接口,而其靜態(tài)方法load的參數(shù),正是指定了被實(shí)現(xiàn)的接口。也因此我們要在服務(wù)提供者的項(xiàng)目的resources目錄下創(chuàng)建這個(gè)目錄并申明接口和對(duì)應(yīng)實(shí)現(xiàn)類的全限定類名。

在Maven項(xiàng)目中,resources目錄就對(duì)應(yīng)的是classpath的根目錄。

簡(jiǎn)而言之,ServiceLoader加載實(shí)現(xiàn)類的過程如下:

  • 先是調(diào)用load方法并指定要掃描的接口
  • 然后掃描項(xiàng)目中META-INF/services目錄,這包括調(diào)用者項(xiàng)目以及它所引入的所有依賴包中的META-INF/services目錄下的聲明
  • 掃描到所有實(shí)現(xiàn)類后,根據(jù)其類名,先判斷是否跟SPI接口為同一類型,如果是則利用反射的方式將所有實(shí)現(xiàn)類實(shí)例化,加載進(jìn)內(nèi)存,并返回所有實(shí)現(xiàn)類的實(shí)例列表

可見,這就是JDK中SPI機(jī)制加載服務(wù)的大致過程,事實(shí)上,現(xiàn)在很多框架也利用SPI機(jī)制實(shí)現(xiàn)了靈活地?cái)U(kuò)展。

示例倉(cāng)庫(kù)地址:傳送門

以上就是一文帶你掌握J(rèn)ava SPI的原理和實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Java SPI的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java實(shí)現(xiàn)通訊錄管理系統(tǒng)項(xiàng)目

    Java實(shí)現(xiàn)通訊錄管理系統(tǒng)項(xiàng)目

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)通訊錄管理系統(tǒng)項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • SpringCloud Gateway路由核心原理解析

    SpringCloud Gateway路由核心原理解析

    本文主要介紹了SpringCloudGateway的基礎(chǔ)構(gòu)建塊、工作原理以及核心原理解析,SpringCloudGateway是Spring官方基于SpringSpringBoot和ProjectReactor等技術(shù)開發(fā)的網(wǎng)關(guān),旨在為微服務(wù)架構(gòu)提供一種簡(jiǎn)單而有效的統(tǒng)一的API路由管理方式
    2024-10-10
  • Java之String.format()方法案例講解

    Java之String.format()方法案例講解

    這篇文章主要介紹了Java之String.format()方法案例講解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • JAVA應(yīng)用系統(tǒng)工具快捷托盤實(shí)例代碼

    JAVA應(yīng)用系統(tǒng)工具快捷托盤實(shí)例代碼

    JAVA應(yīng)用系統(tǒng)工具快捷托盤實(shí)例代碼,需要的朋友可以參考一下
    2013-02-02
  • SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例

    SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例

    本文主要介紹了SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Java 添加文本框到PPT幻燈片過程解析

    Java 添加文本框到PPT幻燈片過程解析

    這篇文章主要介紹了Java 添加文本框到PPT幻燈片過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • 高并發(fā)系統(tǒng)的限流詳解及實(shí)現(xiàn)

    高并發(fā)系統(tǒng)的限流詳解及實(shí)現(xiàn)

    這篇文章主要介紹了高并發(fā)系統(tǒng)的限流詳解及實(shí)現(xiàn),內(nèi)容詳細(xì),小編覺得很不錯(cuò),這里分享給大家,供需要的朋友參考。隨小編一起看看吧。
    2017-11-11
  • Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門教程

    Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門教程

    這篇文章主要介紹了Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門,commons io: 是針對(duì)開發(fā)IO流功能的工具類庫(kù),其中包含了許多可調(diào)用的函數(shù),感興趣的朋友跟隨小編一起看看吧
    2021-04-04
  • Springboot快速入門教程

    Springboot快速入門教程

    今天給大家?guī)?lái)的是關(guān)于Springboot基礎(chǔ)的相關(guān)知識(shí),文章圍繞著Springboot的基礎(chǔ)知識(shí)及用法展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • JAVA多線程與并發(fā)學(xué)習(xí)總結(jié)分析

    JAVA多線程與并發(fā)學(xué)習(xí)總結(jié)分析

    以下是對(duì)小編對(duì)JAVA多線程與并發(fā)的學(xué)習(xí)進(jìn)行了總結(jié)介紹,需要的朋友可以過來(lái)參考下
    2013-08-08

最新評(píng)論

阳信县| 临漳县| 衡南县| 汉源县| 蒙山县| 荃湾区| 灵武市| 宜兰县| 木里| 特克斯县| 兴文县| 永胜县| 苗栗县| 鹤庆县| 兴隆县| 邵东县| 石门县| 出国| 镇江市| 古丈县| 平湖市| 周宁县| 安泽县| 宁安市| 都昌县| 安塞县| 舟山市| 绥芬河市| 洛扎县| 宁陵县| 班玛县| 柳州市| 怀远县| 大荔县| 拉萨市| 原阳县| 那坡县| 齐河县| 三明市| 钦州市| 永川市|