中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java?Spring?Dubbo三種SPI機制的區別是什么

發布時間:2022-08-29 16:34:00 來源:億速云 閱讀:178 作者:iii 欄目:開發技術

今天小編給大家分享一下Java Spring Dubbo三種SPI機制的區別是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

SPI 有什么用?

舉個栗子,現在我們設計了一款全新的日志框架:「super-logger」。默認以XML文件作為我們這款日志的配置文件,并設計了一個配置文件解析的接口:

package com.github.kongwu.spisamples;

public interface SuperLoggerConfiguration {
void configure(String configFile);
}

然后來一個默認的XML實現:

package com.github.kongwu.spisamples;
public class XMLConfiguration implements SuperLoggerConfiguration{
public void configure(String configFile){
......
}
}

那么我們在初始化,解析配置時,只需要調用這個XMLConfiguration來解析XML配置文件即可

package com.github.kongwu.spisamples;

public class LoggerFactory {
static {
SuperLoggerConfiguration configuration = new XMLConfiguration();
configuration.configure(configFile);
}

public static getLogger(Class clazz){
......
}
}

這樣就完成了一個基礎的模型,看起來也沒什么問題。不過擴展性不太好,因為如果想定制/擴展/重寫解析功能的話,我還得重新定義入口的代碼,LoggerFactory 也得重寫,不夠靈活,侵入性太強了。

比如現在用戶/使用方想增加一個 yml 文件的方式,作為日志配置文件,那么只需要新建一個YAMLConfiguration,實現 SuperLoggerConfiguration 就可以。但是……怎么注入呢,怎么讓 LoggerFactory中使用新建的這個 YAMLConfiguration ?難不成連 LoggerFactory 也重寫了?

如果借助SPI機制的話,這個事情就很簡單了,可以很方便的完成這個入口的擴展功能。

下面就先來看看,利用JDK 的 SPI 機制怎么解決上面的擴展性問題。

JDK SPI

JDK 中 提供了一個 SPI 的功能,核心類是 java.util.ServiceLoader。其作用就是,可以通過類名獲取在"META-INF/services/"下的多個配置實現文件。

為了解決上面的擴展問題,現在我們在META-INF/services/下創建一個com.github.kongwu.spisamples.SuperLoggerConfiguration文件(沒有后綴)。文件中只有一行代碼,那就是我們默認的com.github.kongwu.spisamples.XMLConfiguration(注意,一個文件里也可以寫多個實現,回車分隔)

META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:

com.github.kongwu.spisamples.XMLConfiguration

然后通過 ServiceLoader 獲取我們的 SPI 機制配置的實現類:

ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator();
SuperLoggerConfiguration configuration;

while(iterator.hasNext()) {
//加載并初始化實現類
configuration = iterator.next();
}

//對最后一個configuration類調用configure方法
configuration.configure(configFile);

最后在調整LoggerFactory中初始化配置的方式為現在的SPI方式:

package com.github.kongwu.spisamples;
public class LoggerFactory {
static {
ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator();
SuperLoggerConfiguration configuration;

while(iterator.hasNext()) {
configuration = iterator.next();//加載并初始化實現類
}
configuration.configure(configFile);
}

public static getLogger(Class clazz){
......
}
}

「等等,這里為什么是用 iterator ? 而不是get之類的只獲取一個實例的方法?」

試想一下,如果是一個固定的get方法,那么get到的是一個固定的實例,SPI 還有什么意義呢?

SPI 的目的,就是增強擴展性。將固定的配置提取出來,通過 SPI 機制來配置。那既然如此,一般都會有一個默認的配置,然后通過 SPI 的文件配置不同的實現,這樣就會存在一個接口多個實現的問題。要是找到多個實現的話,用哪個實現作為最后的實例呢?

所以這里使用iterator來獲取所有的實現類配置。剛才已經在我們這個 「super-logger」 包里增加了默認的SuperLoggerConfiguration 實現。

為了支持 YAML 配置,現在在使用方/用戶的代碼里,增加一個YAMLConfiguration的 SPI 配置:

META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:

com.github.kongwu.spisamples.ext.YAMLConfiguration

此時通過iterator方法,就會獲取到默認的XMLConfiguration和我們擴展的這個YAMLConfiguration兩個配置實現類了。

在上面那段加載的代碼里,我們遍歷iterator,遍歷到最后,我們**使用最后一個實現配置作為最終的實例。

「再等等?最后一個?怎么算最后一個?」

使用方/用戶自定義的的這個 YAMLConfiguration 一定是最后一個嗎?

這個真的不一定,取決于我們運行時的 ClassPath 配置,在前面加載的jar自然在前,最后的jar里的自然當然也在后面。所以「如果用戶的包在ClassPath中的順序比super-logger的包更靠后,才會處于最后一個位置;如果用戶的包位置在前,那么所謂的最后一個仍然是默認的XMLConfiguration。」

舉個栗子,如果我們程序的啟動腳本為:

java -cp super-logger.jar:a.jar:b.jar:main.jar example.Main

默認的XMLConfiguration SPI配置在super-logger.jar,擴展的YAMLConfiguration SPI配置文件在main.jar,那么iterator獲取的最后一個元素一定為YAMLConfiguration。

但這個classpath順序如果反了呢?main.jar 在前,super-logger.jar 在后

java -cp main.jar:super-logger.jar:a.jar:b.jar example.Main

這樣一來,iterator 獲取的最后一個元素又變成了默認的XMLConfiguration,我們使用 JDK SPI 沒啥意義了,獲取的又是第一個,還是默認的XMLConfiguration。

由于這個加載順序(classpath)是由用戶指定的,所以無論我們加載第一個還是最后一個,都有可能會導致加載不到用戶自定義的那個配置。

「所以這也是JDK SPI機制的一個劣勢,無法確認具體加載哪一個實現,也無法加載某個指定的實現,僅靠ClassPath的順序是一個非常不嚴謹的方式」

Dubbo SPI

Dubbo 就是通過 SPI 機制加載所有的組件。不過,Dubbo 并未使用 Java 原生的 SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重要的模塊。基于 SPI,我們可以很容易的對 Dubbo 進行拓展。如果大家想要學習 Dubbo 的源碼,SPI 機制務必弄懂。接下來,我們先來了解一下 Java SPI 與 Dubbo SPI 的用法,然后再來分析 Dubbo SPI 的源碼。 

Dubbo 中實現了一套新的 SPI 機制,功能更強大,也更復雜一些。相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下,配置內容如下(以下demo來自dubbo官方文檔)。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

與 Java SPI 實現類配置不同,Dubbo SPI 是通過鍵值對的方式進行配置,這樣我們可以按需加載指定的實現類。另外在使用時還需要在接口上標注 @SPI 注解。

下面來演示 Dubbo SPI 的用法:

@SPI
public interface Robot {
void sayHello();
}
public class OptimusPrime implements Robot {
@Override
public void sayHello(){
System.out.println("Hello, I am Optimus Prime.");
}
}

public class Bumblebee implements Robot {

@Override
public void sayHello(){
System.out.println("Hello, I am Bumblebee.");
}
}
public class DubboSPITest {

@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}

「Dubbo SPI 和 JDK SPI 最大的區別就在于支持“別名”」,可以通過某個擴展點的別名來獲取固定的擴展點。就像上面的例子中,我可以獲取 Robot 多個 SPI 實現中別名為“optimusPrime”的實現,也可以獲取別名為“bumblebee”的實現,這個功能非常有用!

通過 @SPI 注解的 value 屬性,還可以默認一個“別名”的實現。比如在Dubbo 中,默認的是Dubbo 私有協議:「dubbo protocol - dubbo://」**

來看看Dubbo中協議的接口:

@SPI("dubbo")
public interface Protocol {
......
}

在 Protocol 接口上,增加了一個 @SPI 注解,而注解的 value 值為 Dubbo ,通過 SPI 獲取實現時就會獲取 Protocol SPI 配置中別名為dubbo的那個實現,com.alibaba.dubbo.rpc.Protocol文件如下:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

然后只需要通過getDefaultExtension,就可以獲取到 @SPI 注解上value對應的那個擴展實現了

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension();
//protocol: DubboProtocol

還有一個 Adaptive 的機制,雖然非常靈活,但&hellip;&hellip;用法并不是很“優雅”,這里就不介紹了

Dubbo 的 SPI 中還有一個“加載優先級”,優先加載內置(internal)的,然后加載外部的(external),按優先級順序加載,「如果遇到重復就跳過不會加載」了。

所以如果想靠classpath加載順序去覆蓋內置的擴展,也是個不太理智的做法,原因同上 - 加載順序不嚴謹

Spring SPI

Spring 的 SPI 配置文件是一個固定的文件 - META-INF/spring.factories,功能上和 JDK 的類似,每個接口可以有多個擴展實現,使用起來非常簡單:

//獲取所有factories文件中配置的LoggingSystemFactory
List<LoggingSystemFactory>> factories =
SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);

下面是一段 Spring Boot 中 spring.factories 的配置

# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver

......

Spring SPI 中,將所有的配置放到一個固定的文件中,省去了配置一大堆文件的麻煩。至于多個接口的擴展配置,是用一個文件好,還是每個單獨一個文件好這個,這個問題就見仁見智了(個人喜歡 Spring 這種,干凈利落)。

Spring的SPI 雖然屬于spring-framework(core),但是目前主要用在spring boot中&hellip;&hellip;

和前面兩種 SPI 機制一樣,Spring 也是支持 ClassPath 中存在多個 spring.factories 文件的,加載時會按照 classpath 的順序依次加載這些 spring.factories 文件,添加到一個 ArrayList 中。由于沒有別名,所以也沒有去重的概念,有多少就添加多少。

但由于 Spring 的 SPI 主要用在 Spring Boot 中,而 Spring Boot 中的 ClassLoader 會優先加載項目中的文件,而不是依賴包中的文件。所以如果在你的項目中定義個spring.factories文件,那么你項目中的文件會被第一個加載,得到的Factories中,項目中spring.factories里配置的那個實現類也會排在第一個

如果我們要擴展某個接口的話,只需要在你的項目(spring boot)里新建一個META-INF/spring.factories文件,「只添加你要的那個配置,不要完整的復制一遍 Spring Boot 的 spring.factories 文件然后修改」**
比如我只想添加一個新的 LoggingSystemFactory 實現,那么我只需要新建一個META-INF/spring.factories文件,而不是完整的復制+修改:

org.springframework.boot.logging.LoggingSystemFactory=\
com.example.log4j2demo.Log4J2LoggingSystem.Factory

對比

  • JDK SPI

  • DUBBO SPI

  • Spring SPI

 


文件方式

每個擴展點單獨一個文件

每個擴展點單獨一個文件

所有的擴展點在一個文件

獲取某個固定的實現

不支持,只能按順序獲取所有實現

有“別名”的概念,可以通過名稱獲取擴展點的某個固定實現,配合Dubbo SPI的注解很方便

不支持,只能按順序獲取所有實現。但由于Spring Boot ClassLoader會優先加載用戶代碼中的文件,所以可以保證用戶自定義的spring.factoires文件在第一個,通過獲取第一個factory的方式就可以固定獲取自定義的擴展

其他

支持Dubbo內部的依賴注入,通過目錄來區分Dubbo 內置SPI和外部SPI,優先加載內部,保證內部的優先級最高

文檔完整度

文章 & 三方資料足夠豐富

文檔 & 三方資料足夠豐富

文檔不夠豐富,但由于功能少,使用非常簡單

IDE支持

IDEA 完美支持,有語法提示

三種 SPI 機制對比之下,JDK 內置的機制是最弱雞的,但是由于是 JDK 內置,所以還是有一定應用場景,畢竟不用額外的依賴;Dubbo 的功能最豐富,但機制有點復雜了,而且只能配合 Dubbo 使用,不能完全算是一個獨立的模塊;Spring 的功能和JDK的相差無幾,最大的區別是所有擴展點寫在一個 spring.factories 文件中,也算是一個改進,并且 IDEA 完美支持語法提示。

以上就是“Java Spring Dubbo三種SPI機制的區別是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

汉中市| 永新县| 汉阴县| 九寨沟县| 潍坊市| 丰顺县| 积石山| 沂南县| 洪雅县| 城口县| 万源市| 金湖县| 扎鲁特旗| 疏勒县| 公安县| 买车| 贵港市| 依兰县| 雷山县| 杨浦区| 府谷县| 五寨县| 弋阳县| 福泉市| 桐梓县| 陇西县| 英吉沙县| 桐城市| 凤凰县| 禄丰县| 马关县| 綦江县| 新宁县| 洞口县| 宜兴市| 怀安县| 金平| 武胜县| 扶余县| 宁德市| 徐水县|