日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

什么是SPI機(jī)制

SPI(Service Provider Interface)是JAVA中一種服務(wù)提供者接口的設(shè)計(jì)模式,它提供了一種機(jī)制,允許組件在不同的實(shí)現(xiàn)之間進(jìn)行插拔,從而實(shí)現(xiàn)松耦合的架構(gòu)。SPI通常用于實(shí)現(xiàn)插件化、可擴(kuò)展的應(yīng)用程序,使開(kāi)發(fā)人員能夠輕松地添加、替換或定制系統(tǒng)中的功能模塊。

當(dāng)服務(wù)的提供者提供了一種接口的實(shí)現(xiàn)之后,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,這個(gè)文件里的內(nèi)容就是這個(gè)接口的具體的實(shí)現(xiàn)類(lèi)。當(dāng)其他的程序需要這個(gè)服務(wù)的時(shí)候,就可以通過(guò)查找這個(gè)jar包(一般都是以jar包做依賴(lài))的META-INF/services/中的配置文件,配置文件中有接口的具體實(shí)現(xiàn)類(lèi)名,可以根據(jù)這個(gè)類(lèi)名進(jìn)行加載實(shí)例化,就可以使用該服務(wù)了。JDK中查找服務(wù)的實(shí)現(xiàn)的工具類(lèi)是:java.util.ServiceLoader

SPI示例

一個(gè)常見(jiàn)的 SPI 示例是 Java 的日志框架 SLF4J(Simple Logging Facade for Java)。SLF4J 允許應(yīng)用程序在運(yùn)行時(shí)選擇不同的日志實(shí)現(xiàn),而無(wú)需修改代碼。以下是一個(gè)簡(jiǎn)化的示例:

  1. 定義日志接口: 首先,定義一個(gè)日志接口,例如 Logger,其中包含了常見(jiàn)的日志方法,如 info(), debug(), error() 等。

 
java
復(fù)制代碼
public interface Logger { void info(String message); void debug(String message); void error(String message); // ...其他日志方法 }
  1. 編寫(xiě)日志實(shí)現(xiàn)類(lèi): 然后,為不同的日志實(shí)現(xiàn)編寫(xiě)實(shí)現(xiàn)類(lèi),這些實(shí)現(xiàn)類(lèi)分別對(duì)接各種日志框架,比如 Log4j、Logback、JDK Logging 等。

 
java
復(fù)制代碼
// Log4jLogger.java public class Log4jLogger implements Logger { private org.Apache.log4j.Logger logger; public Log4jLogger(Class<?> clazz) { logger = org.apache.log4j.Logger.getLogger(clazz); } // 實(shí)現(xiàn) Logger 接口的方法,使用 Log4j 進(jìn)行日志記錄 // ... }
  1. SPI 配置文件:META-INF/services 目錄下創(chuàng)建一個(gè)文件,以接口全限定名為文件名,寫(xiě)入實(shí)現(xiàn)類(lèi)的全限定名。對(duì)于 SLF4J,該文件名為 org.slf4j.Logger

 
shell
復(fù)制代碼
# 文件:META-INF/services/org.slf4j.Logger com.example.logging.Log4jLogger
  1. 使用日志接口: 在應(yīng)用程序中,您只需要使用 SLF4J 提供的接口進(jìn)行日志記錄,而不需要關(guān)心具體的日志實(shí)現(xiàn)。

 
java
復(fù)制代碼
import org.slf4j.LoggerFactory; public class MAIn { private static final Logger logger = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { logger.info("This is an info message."); logger.debug("This is a debug message."); logger.error("This is an error message."); } }

通過(guò)這種方式,我們可以輕松地更改底層的日志實(shí)現(xiàn),而不需要修改應(yīng)用程序的代碼。這就是 SPI 的一個(gè)實(shí)際應(yīng)用示例,它展示了如何通過(guò)接口、實(shí)現(xiàn)類(lèi)、SPI 配置文件以及運(yùn)行時(shí)加載機(jī)制,實(shí)現(xiàn)插拔式的日志框架。

SPI原理

SPI(Service Provider Interface)的原理涉及 Java 的類(lèi)加載機(jī)制、反射以及配置文件加載。以下是SPI的工作原理:

  1. 接口定義: 首先,您需要定義一個(gè)接口,該接口描述了一組服務(wù)或功能的方法。這個(gè)接口將作為服務(wù)提供者和服務(wù)使用者之間的約定。

  2. 服務(wù)提供者接口: 在SPI中,您通常會(huì)定義一個(gè)專(zhuān)門(mén)的接口,用于服務(wù)提供者注冊(cè)和實(shí)例化。這個(gè)接口可能包括方法用于獲取特定的服務(wù)實(shí)例。

  3. 服務(wù)提供者實(shí)現(xiàn): 不同的模塊、庫(kù)或插件可以提供針對(duì)接口的不同實(shí)現(xiàn)。每個(gè)實(shí)現(xiàn)都需要提供一個(gè)特定的類(lèi),實(shí)現(xiàn)服務(wù)提供者接口,并在實(shí)現(xiàn)類(lèi)中提供相關(guān)的功能代碼。

  4. 服務(wù)配置文件: SPI的核心是一個(gè)配置文件,通常命名為 META-INF/services/<接口全限定名>。在這個(gè)文件中,您列出了實(shí)現(xiàn)您接口的類(lèi)的名稱(chēng)。

    • 對(duì)于每個(gè)接口,都可以在 META-INF/services 目錄下創(chuàng)建一個(gè)以接口全限定名為文件名的文件。
    • 在這個(gè)文件中,每一行包含一個(gè)實(shí)現(xiàn)類(lèi)的全限定名。這些實(shí)現(xiàn)類(lèi)是用于提供特定服務(wù)的。
  5. 加載機(jī)制: 當(dāng)需要使用某個(gè)服務(wù)時(shí),您可以通過(guò) Java 的類(lèi)加載機(jī)制以及反射來(lái)加載并實(shí)例化相應(yīng)的實(shí)現(xiàn)類(lèi)。具體過(guò)程如下:

    • 通過(guò) SPI 配置文件,找到接口對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)的全限定名列表。
    • 使用 ClassLoader 加載這些實(shí)現(xiàn)類(lèi)的類(lèi)對(duì)象。
    • 使用反射實(shí)例化這些類(lèi),得到服務(wù)提供者的實(shí)例。
  6. 服務(wù)使用者: 服務(wù)使用者是通過(guò)服務(wù)提供者接口來(lái)獲取服務(wù)實(shí)例的組件。它可以從已注冊(cè)的服務(wù)提供者中選擇一個(gè)合適的實(shí)現(xiàn),然后使用該實(shí)現(xiàn)的功能。

在運(yùn)行時(shí),SPI機(jī)制允許系統(tǒng)自動(dòng)加載并實(shí)例化服務(wù)提供者的實(shí)現(xiàn)類(lèi),從而實(shí)現(xiàn)插拔式的架構(gòu)。這樣,您可以在不修改核心代碼的情況下,通過(guò)添加新的實(shí)現(xiàn)類(lèi)來(lái)擴(kuò)展系統(tǒng)功能,實(shí)現(xiàn)更好的可擴(kuò)展性和靈活性。

下面我們看下JDK中ServiceLoader<S>方法的具體實(shí)現(xiàn):

首先,ServiceLoader實(shí)現(xiàn)了Iterable接口,所以它有迭代器的屬性,這里主要都是實(shí)現(xiàn)了迭代器的hasNextnext方法。這里主要都是調(diào)用的lookupIterator的相應(yīng)hasNextnext方法,lookupIterator是懶加載迭代器。

其次,LazyIterator中的hasNext方法,靜態(tài)變量PREFIX就是”META-INF/services/”目錄,這也就是為什么需要在classpath下的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件。

最后,通過(guò)反射方法Class.forName()加載類(lèi)對(duì)象,并用newInstance方法將類(lèi)實(shí)例化,并把實(shí)例化后的類(lèi)緩存到providers對(duì)象中,(LinkedHashMap<String,S>類(lèi)型)然后返回實(shí)例對(duì)象。

所以我們可以看到ServiceLoader不是實(shí)例化以后,就去讀取配置文件中的具體實(shí)現(xiàn),并進(jìn)行實(shí)例化。而是等到使用迭代器去遍歷的時(shí)候,才會(huì)加載對(duì)應(yīng)的配置文件去解析,調(diào)用hasNext方法的時(shí)候會(huì)去加載配置文件進(jìn)行解析,調(diào)用next方法的時(shí)候進(jìn)行實(shí)例化并緩存。

所有的配置文件只會(huì)加載一次,服務(wù)提供者也只會(huì)被實(shí)例化一次,重新加載配置文件可使用reload方法

SPI機(jī)制的缺陷

通過(guò)上面的解析,可以發(fā)現(xiàn),我們使用SPI機(jī)制的缺陷:

  • 不能按需加載,需要遍歷所有的實(shí)現(xiàn),并實(shí)例化,然后在循環(huán)中才能找到我們需要的實(shí)現(xiàn)。如果不想用某些實(shí)現(xiàn)類(lèi),或者某些類(lèi)實(shí)例化很耗時(shí),它也被載入并實(shí)例化了,這就造成了浪費(fèi)。
  • 如果擴(kuò)展點(diǎn)加載失敗,連擴(kuò)展點(diǎn)的名稱(chēng)都拿不到了。比如:JDK 標(biāo)準(zhǔn)的 ScriptEngine,通過(guò) getName() 獲取腳本類(lèi)型的名稱(chēng),但如果 RubyScriptEngine 因?yàn)樗蕾?lài)的 jruby.jar 不存在,導(dǎo)致 RubyScriptEngine 類(lèi)加載失敗,這個(gè)失敗原因被吃掉了,和 ruby 對(duì)應(yīng)不起來(lái),當(dāng)用戶執(zhí)行 ruby 腳本時(shí),會(huì)報(bào)不支持 ruby,而不是真正失敗的原因。
  • 多個(gè)并發(fā)多線程使用 ServiceLoader 類(lèi)的實(shí)例是不安全的。

Dubbo SPI機(jī)制

Dubbo 就是通過(guò) SPI 機(jī)制加載所有的組件。不過(guò),Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對(duì)其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個(gè)非常重要的模塊?;?SPI,我們可以很容易的對(duì) Dubbo 進(jìn)行拓展。 Dubbo 中,SPI 主要有兩種用法,一種是加載固定的擴(kuò)展類(lèi),另一種是加載自適應(yīng)擴(kuò)展類(lèi)。這兩種方式會(huì)在下面詳細(xì)的介紹。 需要特別注意的是: 在 Dubbo 中,基于 SPI 擴(kuò)展加載的類(lèi)是單例的。

Dubbo SPI 優(yōu)勢(shì)

  1. 按需加載,Dubbo SPI配置文件采用KV格式存儲(chǔ),key 被稱(chēng)為擴(kuò)展名,當(dāng)我們?cè)跒橐粋€(gè)接口查找具體實(shí)現(xiàn)類(lèi)時(shí),可以指定擴(kuò)展名來(lái)選擇相應(yīng)的擴(kuò)展實(shí)現(xiàn),只實(shí)例化這一個(gè)擴(kuò)展實(shí)現(xiàn)即可,無(wú)須實(shí)例化 SPI 配置文件中的其他擴(kuò)展實(shí)現(xiàn)類(lèi),避免資源浪費(fèi),此外通過(guò) KV 格式的 SPI 配置文件,當(dāng)我們使用的一個(gè)擴(kuò)展實(shí)現(xiàn)類(lèi)所在的 jar 包沒(méi)有引入到項(xiàng)目中時(shí),Dubbo SPI 在拋出異常的時(shí)候,會(huì)攜帶該擴(kuò)展名信息,而不是簡(jiǎn)單地提示擴(kuò)展實(shí)現(xiàn)類(lèi)無(wú)法加載。這些更加準(zhǔn)確的異常信息降低了排查問(wèn)題的難度,提高了排查問(wèn)題的效率。;
  2. 增加擴(kuò)展類(lèi)的 IOC 能力,Dubbo 的擴(kuò)展能力并不僅僅只是發(fā)現(xiàn)擴(kuò)展服務(wù)實(shí)現(xiàn)類(lèi),而是在此基礎(chǔ)上更進(jìn)一步,如果該擴(kuò)展類(lèi)的屬性依賴(lài)其他對(duì)象,則 Dubbo 會(huì)自動(dòng)的完成該依賴(lài)對(duì)象的注入功能;
  3. 增加擴(kuò)展類(lèi)的 AOP 能力,Dubbo 擴(kuò)展能力會(huì)自動(dòng)的發(fā)現(xiàn)擴(kuò)展類(lèi)的包裝類(lèi),完成包裝類(lèi)的構(gòu)造,增強(qiáng)擴(kuò)展類(lèi)的功能;

Dubbo SPI原理

Dubbo 擴(kuò)展加載流程

Dubbo 加載擴(kuò)展的整個(gè)流程如下:

主要步驟為 4 個(gè):

  • 讀取并解析配置文件
  • 緩存所有擴(kuò)展實(shí)現(xiàn)
  • 基于用戶執(zhí)行的擴(kuò)展名,實(shí)例化對(duì)應(yīng)的擴(kuò)展實(shí)現(xiàn)
  • 進(jìn)行擴(kuò)展實(shí)例屬性的 IOC 注入以及實(shí)例化擴(kuò)展的包裝類(lèi),實(shí)現(xiàn) AOP 特性

如何使用 Dubbo 擴(kuò)展能力進(jìn)行擴(kuò)展

下面以擴(kuò)展協(xié)議為例進(jìn)行說(shuō)明如何利用 Dubbo 提供的擴(kuò)展能力擴(kuò)展 Triple 協(xié)議。

(1) 在協(xié)議的實(shí)現(xiàn) jar 包內(nèi)放置文本文件:META-INF/dubbo/org.apache.dubbo.remoting.api.WireProtocol


 
properties
復(fù)制代碼
tri=org.apache.dubbo.rpc.protocol.tri.TripleHttp2Protocol

(2) 實(shí)現(xiàn)類(lèi)內(nèi)容


 
java
復(fù)制代碼
@Activate public class TripleHttp2Protocol extends Http2WireProtocol { // ... }

說(shuō)明下:Http2WireProtocol 實(shí)現(xiàn)了 WireProtocol 接口

(3) Dubbo 配置模塊中,擴(kuò)展點(diǎn)均有對(duì)應(yīng)配置屬性或標(biāo)簽,通過(guò)配置指定使用哪個(gè)擴(kuò)展實(shí)現(xiàn)。比如:


 
xml
復(fù)制代碼
<dubbo:protocol name="tri" />

從上面的擴(kuò)展步驟可以看出,用戶基本在黑盒下就完成了擴(kuò)展。

Dubbo 擴(kuò)展的應(yīng)用

Dubbo 的擴(kuò)展能力非常靈活,在自身功能的實(shí)現(xiàn)上無(wú)處不在。

Dubbo 擴(kuò)展能力使得 Dubbo 項(xiàng)目很方便的切分成一個(gè)一個(gè)的子模塊,實(shí)現(xiàn)熱插拔特性。用戶完全可以基于自身需求,替換 Dubbo 原生實(shí)現(xiàn),來(lái)滿足自身業(yè)務(wù)需求。

Dubbo SPI 源碼分析

上面看了 Dubbo SPI 通過(guò) ExtensionLoader加載擴(kuò)展。 ExtensionLoadergetExtensionLoader 方法獲取一個(gè) ExtensionLoader 實(shí)例,然后再通過(guò) ExtensionLoadergetExtension 方法獲取拓展類(lèi)對(duì)象。這其中,getExtensionLoader 方法用于從緩存中獲取與拓展類(lèi)對(duì)應(yīng)的 ExtensionLoader,若緩存未命中,則創(chuàng)建一個(gè)新的實(shí)例。該方法的邏輯比較簡(jiǎn)單,本章就不進(jìn)行分析了。下面我們從 ExtensionLoadergetExtension 方法作為入口,對(duì)拓展類(lèi)對(duì)象的獲取過(guò)程進(jìn)行詳細(xì)的分析。

carbon (5).png

上面代碼的邏輯比較簡(jiǎn)單,首先檢查緩存,緩存未命中則創(chuàng)建拓展對(duì)象。下面我們來(lái)看一下創(chuàng)建拓展對(duì)象的過(guò)程是怎樣的。

createExtension 方法的邏輯稍復(fù)雜一下,包含了如下的步驟:

  1. 通過(guò) getExtensionClasses 獲取所有的拓展類(lèi)
  2. 通過(guò)反射創(chuàng)建拓展對(duì)象
  3. 向拓展對(duì)象中注入依賴(lài)
  4. 將拓展對(duì)象包裹在相應(yīng)的 WrApper 對(duì)象中

以上步驟中,第一個(gè)步驟是加載拓展類(lèi)的關(guān)鍵,第三和第四個(gè)步驟是 Dubbo IOC 與 AOP 的具體實(shí)現(xiàn)。在接下來(lái)的章節(jié)中,將會(huì)重點(diǎn)分析 getExtensionClasses 方法的邏輯,以及簡(jiǎn)單介紹 Dubbo IOC 的具體實(shí)現(xiàn)。

我們?cè)谕ㄟ^(guò)名稱(chēng)獲取拓展類(lèi)之前,首先需要根據(jù)配置文件解析出拓展項(xiàng)名稱(chēng)到拓展類(lèi)的映射關(guān)系表(Map<名稱(chēng), 拓展類(lèi)>),之后再根據(jù)拓展項(xiàng)名稱(chēng)從映射關(guān)系表中取出相應(yīng)的拓展類(lèi)即可。相關(guān)過(guò)程的代碼分析如下:

這里也是先檢查緩存,若緩存未命中,則通過(guò) synchronized 加鎖。加鎖后再次檢查緩存,并判空。此時(shí)如果 classes 仍為 null,則通過(guò) loadExtensionClasses 加載拓展類(lèi)。下面分析 loadExtensionClasses 方法的邏輯。

loadExtensionClasses 方法總共做了兩件事情,一是對(duì) SPI 注解進(jìn)行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過(guò)程比較簡(jiǎn)單,無(wú)需多說(shuō)。下面我們來(lái)看一下 loadDirectory 做了哪些事情。

loadDirectory 方法先通過(guò) classLoader 獲取所有資源鏈接,然后再通過(guò) loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實(shí)現(xiàn)。

loadResource 方法用于讀取和解析配置文件,并通過(guò)反射加載類(lèi),最后調(diào)用 loadClass 方法進(jìn)行其他操作。loadClass 方法用于主要用于操作緩存,該方法的邏輯如下:

如上,loadClass 方法操作了不同的緩存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,該方法沒(méi)有其他什么邏輯了。


作者:半畝方塘立身
鏈接:https://juejin.cn/post/7271597656118624275
來(lái)源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

分享到:
標(biāo)簽:架構(gòu) 插件
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定