詳解SPI在Dubbo中的應(yīng)用
一、概述
SPI 全稱為 Service Provider Interface,是一種模塊間組件相互引用的機(jī)制。其方案通常是提供方將接口實(shí)現(xiàn)類的全名配置在classPath下的指定文件中,由調(diào)用方讀取并加載。這樣需要替換某個(gè)組件時(shí),只需要引入新的JAR包并在其中包含新的實(shí)現(xiàn)類和配置文件即可,調(diào)用方的代碼無(wú)需任何調(diào)整。優(yōu)秀的SPI框架能夠提供單接口多實(shí)現(xiàn)類時(shí)的優(yōu)先級(jí)選擇,由用戶指定選擇哪個(gè)實(shí)現(xiàn)。
得益于這些能力,SPI對(duì)模塊間的可插拔機(jī)制和動(dòng)態(tài)擴(kuò)展提供了非常好的支撐。
本文將簡(jiǎn)單介紹JDK自帶的SPI,分析SPI和雙親委派的關(guān)系,進(jìn)而重點(diǎn)分析DUBBO的SPI機(jī)制;比較兩者有何不同,DUBBO的SPI帶來(lái)了哪些額外的能力。
二、JDK自帶SPI
提供者在classPath或者jar包的META-INF/services/目錄創(chuàng)建以服務(wù)接口命名的文件,調(diào)用者通過java.util.ServiceLoader加載文件內(nèi)容中指定的實(shí)現(xiàn)類。
2.1、代碼示例
首先定義一個(gè)接口Search
search示例接口
package com.example.studydemo.spi; public interface Search { void search(); }
實(shí)現(xiàn)類FileSearchImpl實(shí)現(xiàn)該接口
文件搜索實(shí)現(xiàn)類
package com.example.studydemo.spi; public class FileSearchImpl implements Search { @Override public void search() { System.out.println("文件搜索"); } }
實(shí)現(xiàn)類DataBaseSearchImpl實(shí)現(xiàn)該接口
數(shù)據(jù)庫(kù)搜索實(shí)現(xiàn)類
package com.example.studydemo.spi; public class DataBaseSearchImpl implements Search { @Override public void search() { System.out.println("數(shù)據(jù)庫(kù)搜索"); } }
在項(xiàng)目的META-INF/services文件夾下,創(chuàng)建Search文件
文件內(nèi)容為:
com.example.studydemo.spi.DataBaseSearchImpl com.example.studydemo.spi.FileSearchImpl
測(cè)試:
import java.util.ServiceLoader; public class JavaSpiTest { public static void main(String[] args) { ServiceLoader<Search> searches = ServiceLoader.load(Search.class); searches.forEach(Search::search); } }
結(jié)果為:
2.2、簡(jiǎn)單分析
ServiceLoader作為JDK提供的一個(gè)服務(wù)實(shí)現(xiàn)查找工具類,調(diào)用自身load方法加載Search接口的所有實(shí)現(xiàn)類,然后可以使用for循環(huán)遍歷實(shí)現(xiàn)類進(jìn)行方法調(diào)用。
有一個(gè)疑問:META-INF/services/目錄是硬編碼的嗎,其它路徑行不行?答案是不行。
跟進(jìn)到ServiceLoader類中,第一行代碼就是private static final String PREFIX = “META-INF/services/”,所以SPI配置文件只能放在classPath或者jar包的這個(gè)指定目錄下面。
ServiceLoader的文件載入路徑
public final class ServiceLoader<S> implements Iterable<S> { //硬編碼寫死了文件路徑 private static final String PREFIX = "META-INF/services/"; // The class or interface representing the service being loaded private final Class<S> service; // The class loader used to locate, load, and instantiate providers private final ClassLoader loader;
JDK SPI的使用比較簡(jiǎn)單,做到了基本的加載擴(kuò)展組件的功能,但有以下幾點(diǎn)不足:
- 需要遍歷所有的實(shí)現(xiàn)并實(shí)例化,想要找到某一個(gè)實(shí)現(xiàn)只能循環(huán)遍歷,一個(gè)一個(gè)匹配;
- 配置文件中只是簡(jiǎn)單的列出了所有的擴(kuò)展實(shí)現(xiàn),而沒有給他們命名,導(dǎo)致在程序中很難去準(zhǔn)確的引用它們;
- 擴(kuò)展之間彼此存在依賴,做不到自動(dòng)注入和裝配,不提供上下文內(nèi)的IOC和AOP功能;
- 擴(kuò)展很難和其他的容器框架集成,比如擴(kuò)展依賴了一個(gè)外部spring容器中的bean,原生的JDK SPI并不支持。
三、SPI與雙親委派
3.1、SPI加載到何處
基于類加載的雙親委派原則,由JDK內(nèi)部加載的class默認(rèn)應(yīng)該歸屬于bootstrap類加載器,那么SPI機(jī)制加載的class是否也屬于bootstrap呢 ?
答案是否定的,原生SPI機(jī)制通過ServiceLoader.load方法由外部指定類加載器,或者默認(rèn)取Thread.currentThread().getContextClassLoader()線程上下文的類加載器,從而避免了class被載入bootstrap加載器。
3.2、SPI是否破壞了雙親委派
雙親委派的本質(zhì)涵義是在rt.jar包和外部class之間建立一道classLoader的鴻溝,即rt.jar內(nèi)的class不應(yīng)由外部classLoader加載,外部class不應(yīng)由bootstrap加載。
SPI僅是提供了一種在JDK代碼內(nèi)部干預(yù)外部class文件加載的機(jī)制,并未強(qiáng)制指定加載到何處;外部的class還是由外部的classLoader加載,未跨越這道鴻溝,也就談不上破壞雙親委派。
原生ServiceLoader的類加載器
//指定類加載器 public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) //默認(rèn)取前線程上下文的類加載器 public static <S> ServiceLoader<S> load(Class<S> service)
四、Dubbo SPI
Dubbo借鑒了Java SPI的思想,與JDK的ServiceLoader相對(duì)應(yīng)的,Dubbo設(shè)計(jì)了ExtensionLoader類,其提供的功能比JDK更為強(qiáng)大。
4.1、基本概念
首先介紹一些基本概念,讓大家有一個(gè)初步的認(rèn)知。
擴(kuò)展點(diǎn)(Extension Point):是一個(gè)Java的接口。
擴(kuò)展(Extension):擴(kuò)展點(diǎn)的實(shí)現(xiàn)類
擴(kuò)展實(shí)例(Extension Instance):擴(kuò)展點(diǎn)實(shí)現(xiàn)類的實(shí)例。
自適應(yīng)擴(kuò)展實(shí)例(Extension Adaptive Instance)
自適應(yīng)擴(kuò)展實(shí)例其實(shí)就是一個(gè)擴(kuò)展類的代理對(duì)象,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口。在調(diào)用擴(kuò)展點(diǎn)的接口方法時(shí),會(huì)根據(jù)實(shí)際的參數(shù)來(lái)決定要使用哪個(gè)擴(kuò)展。
比如一個(gè)Search的擴(kuò)展點(diǎn),有一個(gè)search方法。有兩個(gè)實(shí)現(xiàn)FileSearchImpl和DataBaseSearchImpl。Search的自適應(yīng)實(shí)例在調(diào)用接口方法的時(shí)候,會(huì)根據(jù)search方法中的參數(shù),來(lái)決定要調(diào)用哪個(gè)Search的實(shí)現(xiàn)。
如果方法參數(shù)中有name=FileSearchImpl,那么就調(diào)用FileSearchImpl的search方法。如果name=DataBaseSearchImpl,就調(diào)用DataBaseSearchImpl的search方法。自適應(yīng)擴(kuò)展實(shí)例在Dubbo中的使用非常廣泛。
在Dubbo中每一個(gè)擴(kuò)展點(diǎn)都可以有自適應(yīng)的實(shí)例,如果我們沒有使用@Adaptive人工指定,Dubbo會(huì)使用字節(jié)碼工具自動(dòng)生成一個(gè)。
SPI Annotation
作用于擴(kuò)展點(diǎn)的接口上,表明該接口是一個(gè)擴(kuò)展點(diǎn),可以被Dubbo的ExtentionLoader加載
Adaptive
@Adaptive注解可以使用在類或方法上。用在方法上表示這是一個(gè)自適應(yīng)方法,Dubbo生成自適應(yīng)實(shí)例時(shí)會(huì)在方法中植入動(dòng)態(tài)代理的代碼。方法內(nèi)部會(huì)根據(jù)方法的參數(shù)來(lái)決定使用哪個(gè)擴(kuò)展。
@Adaptive注解用在類上代表該實(shí)現(xiàn)類是一個(gè)自適應(yīng)類,屬于人為指定的場(chǎng)景,Dubbo就不會(huì)為該SPI接口生成代理類,最典型的應(yīng)用如AdaptiveCompiler、AdaptiveExtensionFactory等。
@Adaptive注解的值為字符串?dāng)?shù)組,數(shù)組中的字符串是key值,代碼中要根據(jù)key值來(lái)獲取對(duì)應(yīng)的Value值,進(jìn)而加載相應(yīng)的extension實(shí)例。比如new String[]{“key1”,”key2”},表示會(huì)先在URL中尋找key1的值,
如果找到則使用此值加載extension,如果key1沒有,則尋找key2的值,如果key2也沒有,則使用SPI注解的默認(rèn)值,如果SPI注解沒有默認(rèn)值,則將接口名按照首字母大寫分成多個(gè)部分,
然后以'.'分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會(huì)變成yyy.invoker.wrapper,然后以此名稱做為key到URL尋找,如果仍沒有找到則拋出IllegalStateException異常。
ExtensionLoader
類似于Java SPI的ServiceLoader,負(fù)責(zé)擴(kuò)展的加載和生命周期維護(hù)。ExtensionLoader的作用包括:解析配置文件加載extension類、生成extension實(shí)例并實(shí)現(xiàn)IOC和AOP、創(chuàng)建自適應(yīng)的extension等,下文會(huì)重點(diǎn)分析。
擴(kuò)展名
和Java SPI不同,Dubbo中的擴(kuò)展都有一個(gè)名稱,用于在應(yīng)用中引用它們。比如
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
加載路徑
Java SPI從/META-INF/services目錄加載擴(kuò)展配置,Dubbo從以下路徑去加載擴(kuò)展配置文件:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
其中META-INF/dubbo對(duì)開發(fā)者發(fā)放,META-INF/dubbo/internal 這個(gè)路徑是用來(lái)加載Dubbo內(nèi)部的拓展點(diǎn)的。
4.2、代碼示例
定義一個(gè)接口,標(biāo)注上dubbo的SPI注解,賦予默認(rèn)值,并提供兩個(gè)extension實(shí)現(xiàn)類
package com.example.studydemo.spi; @SPI("dataBase") public interface Search { void search(); }
public class FileSearchImpl implements Search { @Override public void search() { System.out.println("文件搜索"); } }
public class DataBaseSearchImpl implements Search { @Override public void search() { System.out.println("數(shù)據(jù)庫(kù)搜索"); } }
在META-INF/dubbo 路徑下創(chuàng)建Search文件
文件內(nèi)容如下:
dataBase=com.example.studydemo.spi.DataBaseSearchImpl file=com.example.studydemo.spi.FileSearchImpl
編寫測(cè)試類進(jìn)行測(cè)試,內(nèi)容如下:
public class DubboSpiTest { public static void main(String[] args) { ExtensionLoader<Search> extensionLoader = ExtensionLoader.getExtensionLoader(Search.class); Search fileSearch = extensionLoader.getExtension("file"); fileSearch.search(); Search dataBaseSearch = extensionLoader.getExtension("dataBase"); dataBaseSearch.search(); System.out.println(extensionLoader.getDefaultExtensionName()); Search defaultSearch = extensionLoader.getDefaultExtension(); defaultSearch.search(); } }
結(jié)果為:
從代碼示例上來(lái)看,Dubbo SPI與Java SPI在這幾方面是類似的:
- 接口及相應(yīng)的實(shí)現(xiàn)
- 配置文件
- 加載類及加載具體實(shí)現(xiàn)
4.3、源碼分析
下面深入到源碼看看SPI在Dubbo中是怎樣工作的,以Protocol接口為例進(jìn)行分析。
//1、得到Protocol的擴(kuò)展加載對(duì)象extensionLoader,由這個(gè)加載對(duì)象獲得對(duì)應(yīng)的自適應(yīng)擴(kuò)展類 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); //2、根據(jù)擴(kuò)展名獲取對(duì)應(yīng)的擴(kuò)展類 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
在獲取擴(kuò)展實(shí)例前要先獲取Protocol接口的ExtensionLoader組件,通過ExtensionLoader來(lái)獲取相應(yīng)的Protocol實(shí)例Dubbo實(shí)際是為每個(gè)SPI接口都創(chuàng)建了一個(gè)對(duì)應(yīng)的ExtensionLoader。
ExtensionLoader組件
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) throw new IllegalArgumentException("Extension type == null"); if(!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } //EXTENSION_LOADERS為ConcurrentMap,存儲(chǔ)Class對(duì)應(yīng)的ExtensionLoader ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
EXTENSION_LOADERS是一個(gè) ConcurrentMap,以接口Protocol為key,以ExtensionLoader對(duì)象為value;保存的是Protocol擴(kuò)展的加載類,第一次加載的時(shí)候Protocol還沒有自己的接口加載類,需要實(shí)例化一個(gè)。
再看new ExtensionLoader<T>(type) 這個(gè)操作,下面為ExtensionLoader的構(gòu)造方法:
rivate ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
每一個(gè)ExtensionLoader都包含2個(gè)值:type和objectFactory,此例中type就是Protocol,objectFactory就是ExtensionFactory。
對(duì)于ExtensionFactory接口來(lái)說,它的加載類中objectFactory值為null。
對(duì)于其他的接口來(lái)說,objectFactory都是通過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()來(lái)獲??;objectFactory的作用就是為dubbo的IOC提供依賴注入的對(duì)象,可以認(rèn)為是進(jìn)程內(nèi)多個(gè)組件容器的一個(gè)上層引用,
隨著這個(gè)方法的調(diào)用次數(shù)越來(lái)越多,EXTENSION_LOADERS 中存儲(chǔ)的 loader 也會(huì)越來(lái)越多。
自適應(yīng)擴(kuò)展類與IOC
得到ExtensionLoader組件之后,再看如何獲得自適應(yīng)擴(kuò)展實(shí)例。
public T getAdaptiveExtension() { //cachedAdaptiveInstance為緩存的自適應(yīng)對(duì)象,第一次調(diào)用時(shí)還沒有創(chuàng)建自適應(yīng)類,所以instance為null Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if(createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { //創(chuàng)建自適應(yīng)對(duì)象實(shí)例 instance = createAdaptiveExtension(); //將自適應(yīng)對(duì)象放到緩存中 cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
首先從cachedAdaptiveInstance緩存中獲取,第一次調(diào)用時(shí)還沒有相應(yīng)的自適應(yīng)擴(kuò)展,需要?jiǎng)?chuàng)建自適應(yīng)實(shí)例,創(chuàng)建后再將該實(shí)例放到cachedAdaptiveInstance緩存中。
創(chuàng)建自適應(yīng)實(shí)例參考createAdaptiveExtension方法,該方法包含兩部分內(nèi)容:創(chuàng)建自適應(yīng)擴(kuò)展類并利用反射實(shí)例化、利用IOC機(jī)制為該實(shí)例注入屬性。
private T createAdaptiveExtension() { try { //得到自適應(yīng)擴(kuò)展類并利用反射實(shí)例化,然后注入屬性值 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); } }
再來(lái)分析getAdaptiveExtensionClass方法,以Protocol接口為例,該方法會(huì)做以下事情:獲取所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類、如果有自適應(yīng)擴(kuò)展類直接返回、如果沒有則創(chuàng)建自適應(yīng)擴(kuò)展類。
//該動(dòng)態(tài)代理生成的入口 private Class<?> getAdaptiveExtensionClass() { //1.獲取所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類 getExtensionClasses(); //2.如果有自適應(yīng)擴(kuò)展類,則返回 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //3.如果沒有,則創(chuàng)建自適應(yīng)擴(kuò)展類 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
getExtensionClasses方法會(huì)加載所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類,首先從緩存中獲取,緩存中沒有則調(diào)用loadExtensionClasses方法進(jìn)行加載并設(shè)置到緩存中,如下圖所示:
private Map<String, Class<?>> getExtensionClasses() { //從緩存中獲取 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { //從SPI配置文件中解析 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
loadExtensionClasses方法如下:首先獲取SPI注解中的value值,作為默認(rèn)擴(kuò)展名稱,在Protocol接口中SPI注解的value為dubbo,因此DubboProtocol就是Protocol的默認(rèn)實(shí)現(xiàn)擴(kuò)展。其次加載三個(gè)配置路徑下的所有的Protocol接口的擴(kuò)展實(shí)現(xiàn)。
// 此方法已經(jīng)getExtensionClasses方法同步過。 private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); if(value != null && (value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if(names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if(names.length == 1) cachedDefaultName = names[0]; } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); //分別從三個(gè)路徑加載 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; } private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
在加載配置路徑下的實(shí)現(xiàn)中,其中有一個(gè)需要關(guān)注的點(diǎn),如果其中某個(gè)實(shí)現(xiàn)類上有Adaptive注解,說明用戶指定了自適應(yīng)擴(kuò)展類,那么該實(shí)現(xiàn)類就會(huì)被賦給cachedAdaptiveClass,在getAdaptiveExtensionClass方法中會(huì)被直接返回。
如果該變量為空,則需要通過字節(jié)碼工具來(lái)創(chuàng)建自適應(yīng)擴(kuò)展類。
private Class<?> createAdaptiveExtensionClass() { //生成類代碼 String code = createAdaptiveExtensionClassCode(); //找到類加載器 ClassLoader classLoader = findClassLoader(); //獲取編譯器實(shí)現(xiàn)類,此處為AdaptiveCompiler,此類上有Adaptive注解 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); //將類代碼編譯為Class return compiler.compile(code, classLoader); }
createAdaptiveExtensionClass方法生成的類代碼如下:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
由字節(jié)碼工具生成的類Protocol$Adpative在方法末尾調(diào)用了ExtensionLoader.getExtensionLoader(xxx).getExtension(extName)來(lái)滿足adaptive的自適應(yīng)動(dòng)態(tài)特性。
傳入的extName就是從url中獲取的動(dòng)態(tài)參數(shù),用戶只需要在代表DUBBO全局上下文信息的URL中指定protocol參數(shù)的取值,adaptiveExtentionClass就可以去動(dòng)態(tài)適配不同的擴(kuò)展實(shí)例。
再看屬性注入方法injectExtension,針對(duì)public的只有一個(gè)參數(shù)的set方法進(jìn)行處理,利用反射進(jìn)行方法調(diào)用來(lái)實(shí)現(xiàn)屬性注入,此方法是Dubbo SPI實(shí)現(xiàn)IOC功能的關(guān)鍵。
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance;
Dubbo IOC 是通過set方法注入依賴,Dubbo首先會(huì)通過反射獲取到實(shí)例的所有方法,然后再遍歷方法列表,檢測(cè)方法名是否具有set方法特征。若有則通過ObjectFactory獲取依賴對(duì)象。
最后通過反射調(diào)用set方法將依賴設(shè)置到目標(biāo)對(duì)象中。objectFactory在創(chuàng)建加載類ExtensionLoader的時(shí)候已經(jīng)創(chuàng)建了,因?yàn)锧Adaptive是打在類AdaptiveExtensionFactory上,所以此處就是AdaptiveExtensionFactory。
AdaptiveExtensionFactory持有所有ExtensionFactory對(duì)象的集合,dubbo內(nèi)部默認(rèn)實(shí)現(xiàn)的對(duì)象工廠是SpiExtensionFactory和SpringExtensionFactory,他們經(jīng)過TreeSet排好序,查找順序是優(yōu)先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。
//有Adaptive注解說明該類是自適應(yīng)類,不需要程序自己創(chuàng)建代理類 @Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { //factories擁有所有ExtensionFactory接口的實(shí)現(xiàn)對(duì)象 private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } //查找時(shí)會(huì)遍歷factories,順序優(yōu)先從SpiExtensionFactory中獲取,再?gòu)腟pringExtensionFactory中獲取,原因?yàn)槌跏蓟瘯r(shí)getSupportedExtensions方法中使用TreeSet已經(jīng)排序,見下圖 public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; } }
public Set<String> getSupportedExtensions() { Map<String, Class<?>> clazzes = getExtensionClasses(); return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet())); }
雖然有過度設(shè)計(jì)的嫌疑,但我們不得不佩服dubbo SPI設(shè)計(jì)的精巧。
- 提供@Adaptive注解,既可以加在方法上通過參數(shù)動(dòng)態(tài)適配到不同的擴(kuò)展實(shí)例;又可以加在類上直接指定自適應(yīng)擴(kuò)展類。
- 利用AdaptiveExtensionFactory統(tǒng)一了進(jìn)程中的不同容器,將ExtensionLoader本身視為一個(gè)獨(dú)立的容器,依賴注入時(shí)將會(huì)分別從Spring容器和ExtensionLoader容器中查找。
擴(kuò)展實(shí)例和AOP
getExtension方法比較簡(jiǎn)單,重點(diǎn)在于createExtension方法,根據(jù)擴(kuò)展名創(chuàng)建擴(kuò)展實(shí)例。
public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { //根據(jù)擴(kuò)展名創(chuàng)建擴(kuò)展實(shí)例 instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
createExtension方法中的部分內(nèi)容上文已經(jīng)分析過了,getExtensionClasses方法獲取接口的所有實(shí)現(xiàn)類,然后通過name獲取對(duì)應(yīng)的Class。緊接著通過clazz.newInstance()來(lái)實(shí)例化該實(shí)現(xiàn)類,調(diào)用injectExtension為實(shí)例注入屬性。
private T createExtension(String name) { //getExtensionClasses方法之前已經(jīng)分析過,獲取所有的擴(kuò)展類,然后根據(jù)擴(kuò)展名獲取對(duì)應(yīng)的擴(kuò)展類 Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //屬性注入 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) { //包裝類的創(chuàng)建及屬性注入 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); } }
在方法的最后有一段對(duì)于WrapperClass包裝類的處理邏輯,如果接口存在包裝類實(shí)現(xiàn),那么就會(huì)返回包裝類實(shí)例。實(shí)現(xiàn)AOP的關(guān)鍵就是WrapperClass機(jī)制,判斷一個(gè)擴(kuò)展類是否是WrapperClass的依據(jù),是看其constructor函數(shù)中是否包含當(dāng)前接口參數(shù)。
如果有就認(rèn)為是一個(gè)wrapperClass,最終創(chuàng)建的實(shí)例是一個(gè)經(jīng)過多個(gè)wrapperClass層層包裝的結(jié)果;在每個(gè)wrapperClass中都可以編入面向切面的代碼,從而就簡(jiǎn)單實(shí)現(xiàn)了AOP功能。
Activate活性擴(kuò)展
對(duì)應(yīng)ExtensionLoader的getActivateExtension方法,根據(jù)多個(gè)過濾條件從extension集合中智能篩選出您所需的那一部分。
getActivateExtension方法
public List<T> getActivateExtension(URL url, String[] names, String group);
首先這個(gè)方法只會(huì)返回帶有Activate注解的擴(kuò)展類,但并非帶有注解的擴(kuò)展類都會(huì)被返回。
names是明確指定所需要的那部分?jǐn)U展類,非明確指定的擴(kuò)展類需要滿足group過濾條件和Activate注解本身指定的key過濾條件,非明確指定的會(huì)按照Activate注解中指定的排序規(guī)則進(jìn)行排序;
getActivateExtension的返回結(jié)果是上述兩種擴(kuò)展類的總和。
Activate注解類
*/ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Activate { /** * Group過濾條件。 */ String[] group() default {}; /** * Key過濾條件。包含{@link ExtensionLoader#getActivateExtension}的URL的參數(shù)Key中有,則返回?cái)U(kuò)展。 */ String[] value() default {}; /** * 排序信息,可以不提供。 */ String[] before() default {}; /** * 排序信息,可以不提供。 */ String[] after() default {}; /** * 排序信息,可以不提供。 */ int order() default 0; }
活性Extension最典型的應(yīng)用是rpc invoke時(shí)獲取filter鏈條,各種filter有明確的執(zhí)行優(yōu)先級(jí),同時(shí)也可以人為增添某些filter,filter還可以根據(jù)服務(wù)提供者和消費(fèi)者進(jìn)行分組過濾。
以TokenFilter為例,其注解為@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY),表示該過濾器只在服務(wù)提供方才會(huì)被加載,同時(shí)會(huì)驗(yàn)證注冊(cè)地址url中是否帶了token參數(shù),如果有token表示服務(wù)端注冊(cè)時(shí)指明了要做token驗(yàn)證,自然就需要加載該filter。
反之則不用加載;此filter加載后的執(zhí)行邏輯則是從url中獲取服務(wù)端注冊(cè)時(shí)預(yù)設(shè)的token,再?gòu)膔pc請(qǐng)求的attachments中獲取消費(fèi)方設(shè)置的remote token,比較兩者是否一致,若不一致拋出RPCExeption異常阻止消費(fèi)方的正常調(diào)用。
五、總結(jié)
Dubbo 所有的接口幾乎都預(yù)留了擴(kuò)展點(diǎn),根據(jù)用戶參數(shù)來(lái)適配不同的實(shí)現(xiàn)。如果想增加新的接口實(shí)現(xiàn),只需要按照SPI的規(guī)范增加配置文件,并指向新的實(shí)現(xiàn)即可。
用戶配置的Dubbo屬性都會(huì)體現(xiàn)在URL全局上下文參數(shù)中,URL貫穿了整個(gè)Dubbo架構(gòu),是Dubbo各個(gè)layer組件間相互調(diào)用的紐帶。
總結(jié)一下 Dubbo SPI 相對(duì)于 Java SPI 的優(yōu)勢(shì):
- Dubbo的擴(kuò)展機(jī)制設(shè)計(jì)默認(rèn)值,每個(gè)擴(kuò)展類都有自己的名稱,方便查找。
- Dubbo的擴(kuò)展機(jī)制支持IOC,AOP等高級(jí)功能。
- Dubbo的擴(kuò)展機(jī)制能和第三方IOC容器兼容,默認(rèn)支持Spring Bean,也可擴(kuò)展支持其他容器。
- Dubbo的擴(kuò)展類通過@Adaptive注解實(shí)現(xiàn)了動(dòng)態(tài)代理功能,更強(qiáng)大的是它可以通過一個(gè)proxy映射多個(gè)不同的擴(kuò)展類。
- Dubbo的擴(kuò)展類通過@Activate注解實(shí)現(xiàn)了不同擴(kuò)展類的分組、過濾、排序功能,能夠更好的適配較復(fù)雜的業(yè)務(wù)場(chǎng)景。
以上就是詳解SPI在Dubbo中的應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于SPI在Dubbo中的應(yīng)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot遇到的坑@Qualifier報(bào)紅的解決
這篇文章主要介紹了SpringBoot遇到的坑@Qualifier報(bào)紅的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池的方法
這篇文章主要介紹了Java實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池的方法,涉及java數(shù)據(jù)庫(kù)連接池的創(chuàng)建、連接、刷新、關(guān)閉及狀態(tài)獲取的常用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Springboot接口項(xiàng)目如何使用AOP記錄日志
這篇文章主要介紹了Springboot接口項(xiàng)目如何使用AOP記錄日志,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Spring Boot假死診斷實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于Spring Boot假死診斷的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07超級(jí)好用的輕量級(jí)JSON處理命令jq(最新推薦)
jq是一個(gè)輕量級(jí)的命令行工具,讓你可以非常方便地處理JSON數(shù)據(jù),如切分、過濾、映射、轉(zhuǎn)化等,就像sed、awk、grep文本處理三劍客一樣,這篇文章主要介紹了超級(jí)好用的輕量級(jí)JSON處理命令jq,需要的朋友可以參考下2023-01-01基于Java實(shí)現(xiàn)簡(jiǎn)單的身材計(jì)算程序
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)簡(jiǎn)單的身材計(jì)算程序,可以計(jì)算身體的體脂率以及BMI數(shù)值等,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12