前言
依賴自動發現機制是爲了提高程序的擴展性,降低代碼的耦合度。在不對核心代碼做修改和配置的前提下,只需在classpath下添加依賴,就能將插件接入到系統中。
遵循的基本原則
面向接口編程
核心代碼面向接口編程
約定大於配置
根據約定目錄下的配置,讀取並加載接口實現類
JDK自帶的SPI
SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制(動態替換接口實現)
java spi的具體約定爲:當服務的提供者,提供了服務接口的一種實現之後,在jar包的META-INF/services/目錄裏同時創建一個以服務接口命名的文件。該文件裏就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。 基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼裏制定。jdk提供服務實現查找的一個工具類:java.util.ServiceLoader
通常用來爲框架提供擴展性、兼容性和第三方插件接口:最常見的就是JDBC驅動,不同的數據廠家對應不同的驅動
例如, 有個接口,想運行時動態的給它添加實現,你只需要添加一個實現的依賴包到classpath,在這個依賴包工程中,通過一個文件聲明實現類與SPI的對應關係
在classpath下的
MATE-INF/services
目錄下創建一個文件,文件名爲JDK的SPI全路徑名,文件內容爲當前依賴包中的該SPI的實現類(可以有多個,用逗號隔開)。文件編碼必須是UTF-8
當JVM啓動時,會掃描classpath MATE-INF/services
目錄下所有的文件,得到SPI與實現類的映射關係
通過ServiceLoader來加載SPI對應的實現類
ServiceLoader<HelloInterface> loaders = ServiceLoader.load(HelloInterface);
for (HelloInterface hello : loaders) {
hello.sayHello();
}
Dubbo實現的SPI
Dubbo的擴展機制是基於SPI思想來實現的,但是並沒有採用JDK中原生的SPI機制,因爲JDK中SPI具有很大的缺點:
JDK中標準的SPI會一次性實例化擴展點所有的實現類,不管這些實例化出來的擴展點實現有沒有被用到。有的擴展點實現初始化時非常的耗時,即使沒有用到也會被加載,這樣就很浪費資源
Dubbo中的SPI概述
Dubbo的SPI機制中增加了對擴展點IOC和AOP的支持,一個擴展點可以直接setter注入到其他的擴展點中
彌補了上述JDK中SPI的缺點,結合Spring的設計思想,細粒度的控制擴展實現類的實例化
SPI在dubbo中的用處
Dubbo作爲靈活的框架,並不會強制所有用戶都一定使用Dubbo提供的某些架構,提供用戶自定義插件的接入
例如註冊中心(Registry),Dubbo提供了zk和redis,但是如果我們更傾向於其他的註冊中心的話,我們可以替換掉Dubbo提供的註冊中心,可以寫一個類似eureka server的註冊中心,替換默認的zk
切換zk和redis,只用在應用層使用dubbo提供的api進行配置;如果想要使用其他的註冊中心實現,可以利用dubbo的SPI機制,添加自定義的註冊中心實現依賴
針對這種可被替換的技術實現點我們稱之爲擴展點,類似的擴展點還有很多,例如Protocol,Filter,Loadbalance等等
微內核
微內核架構 (Microkernel architecture) 模式也被稱爲插件架構 (Plugin architecture) 模式
原本與內核集成在一起的組件會被分離出來,內核提供了特定的接口使得這些組件可以靈活的接入,這些組件在內核的管理下工作,但是這些組件可以獨立的發展、更改(不會對現有系統造成改動),只要符合內核的接口即可。典型的例子比如,Eclipse,IDEA
Dubbo內核對外暴露出擴展點,通過擴展點可以實現定製的符合自己業務需求的功能
Dubbo內核通過ExtensionLoader擴展點加載器來加載各個SPI擴展點。Dubbo內核對擴展是無感的 ,完全不知道擴展的存在 ,內核代碼中不會出現使用具體擴展的硬編碼
術語
擴展點
dubbo中被@SPI註解的接口被稱爲擴展點
擴展
被 @SPI 註解的 Interface 的實現稱爲這個擴展點的一個擴展
實現原理
以rpc模塊爲例
規約
擴展點約定
擴展點必須是 Interface 類型 ,必須被 @SPI 註解 ,滿足這兩點纔是一個擴展點
擴展定義約定
META-INF/services/$擴展點接口的全類名
META-INF/dubbo/$擴展點接口的全類名
META-INF/dubbo/internal/$擴展點接口的全類名
這些路徑下定義的
文件名稱爲 $擴展點接口的全類名
(當前項目中定義的SPI接口-被@SPI
標記) :
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
文件內容是以鍵值對的方式配置擴展點的擴展實現:
echo=org.apache.dubbo.rpc.filter.EchoFilter
generic=org.apache.dubbo.rpc.filter.GenericFilter
genericimpl=org.apache.dubbo.rpc.filter.GenericImplFilter
token=org.apache.dubbo.rpc.filter.TokenFilter
accesslog=org.apache.dubbo.rpc.filter.AccessLogFilter
activelimit=org.apache.dubbo.rpc.filter.ActiveLimitFilter
classloader=org.apache.dubbo.rpc.filter.ClassLoaderFilter
context=org.apache.dubbo.rpc.filter.ContextFilter
consumercontext=org.apache.dubbo.rpc.filter.ConsumerContextFilter
exception=org.apache.dubbo.rpc.filter.ExceptionFilter
executelimit=org.apache.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=org.apache.dubbo.rpc.filter.DeprecatedFilter
compatible=org.apache.dubbo.rpc.filter.CompatibleFilter
timeout=org.apache.dubbo.rpc.filter.TimeoutFilter
默認適應擴展
Protocol SPI被標記了@SPI(“dubbo”),表示Protocol的實現類默認使用DubboProtocol
其實dubbo和實現類DubboProtocol關係類似Spring配置文件中的id和class的關係,不同的是Dubbo的關係是配置在目錄/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
文件中,文件內容爲:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
實現機制就類似Spring的bean注入,通過key(dubbo、http、hessian)來找到其實現類
核心實現 - 擴展加載器 ExtensionLoader
擴展加載器控制着 dubbo 內部所有擴展點的初始化、加載擴展的過程
ExtensionLoader 中會存儲兩個靜態屬性 :
- EXTENSION_LOADERS 保存了內核開放的擴展點對應的 ExtensionLoader 實例對象 (說明了一種擴展點有一個對應的 ExtensionLoader 對象)
- EXTENSION_INSTANCES 保存了擴展類型 (Class) 和擴展類型的實例對象。
SpringBoot的組件自動裝配
spring-boot-starter-XXX
在使用SpringBoot構建項目時,當需要某個領域的功能時(例如web服務、數據訪問等),只需要在classpath下添加(pom配置文件配置)相應的spring-boot-starter-xxx依賴,然後在啓動類添加開啓對應的組件功能的註解(@EnableXxx
和XxxAutoCinfiguration
),就可以將組件接入到SpringBoot中
SpringCloud就是利用這種機制,在SpringBoot的基礎上開發的微服務工具集
實現
spring-boot-starter-XXX
中沒有任何業務實現,作爲橋接器,將組件功能接入到SpringBoot中(被封裝成bean注入到容器中)
SpringBoot與前兩種SPI模式模式的不同在於不是簡單的面向接口,而是面向Spring上下文(通過bean注入和取用的方式來調用組件功能),耦合度更低
橋接邏輯
配置依賴
配置spring-boot-starter-XXX
的pom文件,添加這個組件具體實現的依賴,以及AutoConfiguration的相關依賴
定義橋接邏輯
XxxAutoConfiguration ①
由@Configuration
標記
在配置類中完成:加載配置、環境檢查、第三方功能入口bean
XxxConfiguration ②
實現ApplicationContextAware
獲取操作上下文能力
組件註冊
通過元數據的配置(/MATE-INF/spring.factories
或註解)的方式來註冊組件
配置spring.factories
①
在starter組件工程的classpath下創建/MATE-INF/spring.factories
,在這個文件中配置自定義的AutoConfiguration類
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.xxx.XXXAutoConfiguration
添加@EnableXxx ②
在spring-boot-starter-XXX
中定義,通過@Import
指定對應的Configuration類(與AutoConfiguration實現不同,Configuration是實現ApplicationContextAware來獲取對上下文applicationContext的操作能力)
所以這個模式的操作空間更多
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
加載接入
在SpringBoot應用啓動時,spring boot會遍歷在各個jar包種META-INF目錄下的spring.factories文件,構建成一個配置類鏈表(按@AutoConfigureAfter的順序),然後按順序執行配置類,將符合條件的bean注入到SpringBoot中的容器中