1.背景,改進
SPI的全名爲Service Provider Interface,面向對象的設計裏面,模塊之間推薦基於接口編程,而不是對實現類進行硬編碼,這樣做也是爲了模塊設計的可拔插原則。爲了在模塊裝配的時候不在程序裏指明是哪個實現,就需要一種服務發現的機制,jdk的spi就是爲某個接口尋找服務實現
瞭解Dubbo/閱讀過官網文檔的同學,可以看到Dubbo內部存在着大量的擴展,如Protocol:DubboProtocol,InJvmProtocol; Cluster:FailoverCluster,FailFastCluser;LoadBalance:RandomLoadBalance,LeastActiveLoadBalance等等;
Dubbo的SPI擴展機制主要在JDK的擴展機制上進行了改進:主要原因如下
- 需要遍歷所有的實現,並實例化,然後我們在循環中才能找到我們需要的實現。
- 配置文件中只是簡單的列出了所有的擴展實現,而沒有給他們命名。導致在程序中很難去準確的引用它們。
- 擴展如果依賴其他的擴展,做不到自動注入和裝配
- 不提供類似於Spring的IOC和AOP功能
- 擴展很難和其他的框架集成,比如擴展裏面依賴了一個Spring bean,原生的Java SPI不支持
2.Dubbo—SPI常見術語(摘自官網)
2.1 擴展點(Extension Point)
是一個Java的接口。
2.2 擴展(Extension)
擴展點的實現類。
2.3 擴展實例(Extension Instance)
擴展點實現類的實例。
2.4 擴展自適應實例(Extension Adaptive Instance)
第一次接觸這個概念時,可能不太好理解(我第一次也是這樣的...)。如果稱它爲擴展代理類,可能更好理解些。擴展的自適應 實例其實就是一個Extension的代理,它實現了擴展點接口。在調用擴展點的接口方法時,會根據實際的參數來決定要使用哪個擴 展。比如一個IRepository的擴展點,有一個save方法。有兩個實現MysqlRepository和MongoRepository。IRepository的自適應實例在調用接口方法的時候,會根據save方法中的參數,來決定要調用哪個IRepository的實現。如果方法參數中有repository=mysql,那麼就調用MysqlRepository的save方法。如果repository=mongo,就調用MongoRepository的save方法。和麪向對象的延遲綁定很類似。爲什麼Dubbo會引入擴展自適應實例的概念呢?
- Dubbo中的配置有兩種,一種是固定的系統級別的配置,在Dubbo啓動之後就不會再改了。還有一種是運行時的配置,可能對於每一次的RPC,這些配置都不同。比如在xml文件中配置了超時時間是10秒鐘,這個配置在Dubbo啓動之後,就不會改變了。但針對某一次的RPC調用,可以設置它的超時時間是30秒鐘,以覆蓋系統級別的配置。對於Dubbo而言,每一次的RPC調用的參數都是未知的。只有在運行時,根據這些參數才能做出正確的決定。
- 很多時候,我們的類都是一個單例的,比如Spring的bean,在Spring bean都實例化時,如果它依賴某個擴展點,但是在bean實例化時,是不知道究竟該使用哪個具體的擴展實現的。這時候就需要一個代理模式了,它實現了擴展點接口,方法內部可以根據運行時參數,動態的選擇合適的擴展實現。而這個代理就是自適應實例。 自適應擴展實例在Dubbo中的使用非常廣泛,Dubbo中,每一個擴展都會有一個自適應類,如果我們沒有提供,Dubbo會使用字節碼工具爲我們自動生成一個。所以我們基本感覺不到自適應類的存在。後面會有例子說明自適應類是怎麼工作的。
2.5 @SPI
@SPI註解作用於擴展點的接口上,表明該接口是一個擴展點。可以被Dubbo的ExtentionLoader加載。如果沒有此ExtensionLoader調用會異常。
2.6 @Adaptive
@Adaptive註解用在擴展接口的方法上。表示該方法是一個自適應方法。Dubbo在爲擴展點生成自適應實例時,如果方法有@Adaptive註解,會爲該方法生成對應的代碼。方法內部會根據方法的參數,來決定使用哪個擴展。 @Adaptive註解用在類上代表實現一個裝飾類,類似於設計模式中的裝飾模式,它主要作用是返回指定類,目前在整個系統中AdaptiveCompiler、AdaptiveExtensionFactory這兩個類擁有該註解。
2.7 ExtentionLoader
類似於Java SPI的ServiceLoader,負責擴展的加載和生命週期維護。
2.8 擴展別名
和Java SPI不同,Dubbo中的擴展都有一個別名,用於在應用中引用它們。比如
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
其中的random,roundrobin就是對應擴展的別名。這樣我們在配置文件中使用random或roundrobin就可以了。
2.9 一些路徑
和Java SPI從/META-INF/services
目錄加載擴展配置類似,Dubbo也會從以下路徑去加載擴展配置文件:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
3.Dubbo-SPI機制測試
項測項目結構,定義Roboot接口,定義void sayHello()方法,注意Roboot接口定義SPI註解,SPI(key)表示默認爲value(對應鍵值對中的key);
兩個Roboot實現類,Bumbleee,OptimusPrime;
Bumbleee類:
package com.didispace.spi.impl;
import com.didispace.spi.Robot;
/**
* @Author: SoftWareKang
* @Name:SpringCloud-Learning
* @Date: 2020/5/24 17:33
*/
public class Bumblebee implements Robot {
public static final String NAME = "bumblebee";
@Override
public void sayHello() {
System.out.println("I'm Bumblebee");
}
}
OptimusPrime類:
package com.didispace.spi.impl;
import com.didispace.spi.Robot;
/**
* @Author: SoftWareKang
* @Name:SpringCloud-Learning
* @Date: 2020/5/24 17:33
*/
public class OptimusPrime implements Robot {
public static final String NAME = "optimusPrime";
@Override
public void sayHello() {
System.out.println("I'm OptimusPrime");
}
}
key-value文件:META-INF/dubbo/com.didispace.spi.Robot,文件內容如下
optimusPrime = com.didispace.spi.impl.OptimusPrime bumblebee = com.didispace.spi.impl.Bumblebee
測試類:
@Test
public void spiTest() {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
測試結果:控制檯會打印如下結果;
I'm OptimusPrime
I'm Bumblebee
4.基本用法瞭解,探測源碼
ExtensionLoader 是最核心的類,負責擴展點的加載和生命週期管理。 ExtensionLoader 的方法比較多,比較常用的方法有:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) public T getExtension(String name) public T getAdaptiveExtension()
getExtensionLoader方法 這是一個靜態工廠方法,入參是一個可擴展的接口,返回一個該接口的ExtensionLoader實體類。通過這個實體類,可以根據name獲得具體的擴展,也可以獲得一個自適應擴展。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// type如果爲空返回參數異常;
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
// 如果type不是接口類型,返回參數異常
} else if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
// 如果沒有@SPI註解,返回參數異常
} else if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
} else {
// 獲取ExtensionLoader實例
ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if (loader == null) {
// 放入緩存中
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
// loader賦值
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
}
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
// 根據name即key獲取對用接口實現類的實例
public T getExtension(String name) {
// 判斷參數:如果name不爲null或不爲"",否則返回參數異常
if (name != null && name.length() != 0) {
if ("true".equals(name)) {
return this.getDefaultExtension();
} else {
// 緩存中獲取holdler:可以理解爲對一個對象持有的工具類
Holder<Object> holder = (Holder)this.cachedInstances.get(name);
if (holder == null) {
// 沒有則創建holder
this.cachedInstances.putIfAbsent(name, new Holder());
holder = (Holder)this.cachedInstances.get(name);
}
// 獲取該對象實例的值
Object instance = holder.get();
// 標準的雙重判斷
if (instance == null) {
synchronized(holder) {
instance = holder.get();
if (instance == null) {
// 核心方法,創建實例
instance = this.createExtension(name);
holder.set(instance);
}
}
}
return instance;
}
} else {
throw new IllegalArgumentException("Extension name == null");
}
}
// holder類
public class Holder<T> {
private volatile T value;
public Holder() {
}
public void set(T value) {
this.value = value;
}
public T get() {
return this.value;
}
}
核心方法private T createExtension(String name)方法
private T createExtension(String name) {
// 根據擴展節點名稱獲取對應class
Class<?> clazz = (Class)this.getExtensionClasses().get(name);
if (clazz == null) {
throw this.findException(name);
} else {
try {
// 緩存中獲取實例
T instance = EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 如果爲空,調用newInstance()方法生成實例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = EXTENSION_INSTANCES.get(clazz);
}
// IOC注入屬性
this.injectExtension(instance);
// 如果有wrapper,添加wrapper
Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
Class wrapperClass;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for(Iterator i$ = wrapperClasses.iterator(); i$.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
wrapperClass = (Class)i$.next();
}
}
return instance;
} catch (Throwable var7) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + this.type + ") could not be instantiated: " + var7.getMessage(), var7);
}
}
}
核心: private Map<String, Class<?>> getExtensionClasses()方法
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = (Map)this.cachedClasses.get();
if (classes == null) {
Holder var2 = this.cachedClasses;
synchronized(this.cachedClasses) {
classes = (Map)this.cachedClasses.get();
if (classes == null) {
// 根據配置,加載classes
classes = this.loadExtensionClasses();
this.cachedClasses.set(classes);
}
}
}
return classes;
}
核心:private Map<String, Class<?>> loadExtensionClasses()方法,private void loadFile(Map<String, Class<?>> extensionClasses, String dir)方法
private Map<String, Class<?>> loadExtensionClasses() {
// 獲取SPI默認值
SPI defaultAnnotation = (SPI)this.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 " + this.type.getName() + ": " + Arrays.toString(names));
}
if (names.length == 1) {
// 緩存
this.cachedDefaultName = names[0];
}
}
}
Map<String, Class<?>> extensionClasses = new HashMap();
// 加載三個目錄下的配置
this.loadFile(extensionClasses, "META-INF/dubbo/internal/");
this.loadFile(extensionClasses, "META-INF/dubbo/");
this.loadFile(extensionClasses, "META-INF/services/");
return extensionClasses;
}
// 加載的核心方法,extensionClasses緩存class
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
// 拼接fileName
String fileName = dir + this.type.getName();
try {
// 加載器
ClassLoader classLoader = findClassLoader();
Enumeration urls;
// 根據文件名獲取所有URL
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
label269:
while(urls.hasMoreElements()) {
java.net.URL url = (java.net.URL)urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while(true) {
do {
if ((line = reader.readLine()) == null) {
continue label269;
}
// 定位#字符,截取,因爲是註釋
int ci = line.indexOf(35);
if (ci >= 0) {
line = line.substring(0, ci);
}
// 去除空格
line = line.trim();
} while(line.length() <= 0);
try {
String name = null;
int i = line.indexOf(61);
if (i > 0) {
// 獲取key-value
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// class.forname獲取class
Class<?> clazz = Class.forName(line, true, classLoader);
if (!this.type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " + this.type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (this.cachedAdaptiveClass == null) {
this.cachedAdaptiveClass = clazz;
} else if (!this.cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: " + this.cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
}
} else {
try {
// // 檢測 clazz 是否有默認的構造方法,如果沒有,則拋出異常
clazz.getConstructor(this.type);
Set<Class<?>> wrappers = this.cachedWrapperClasses;
if (wrappers == null) {
this.cachedWrapperClasses = new ConcurrentHashSet();
wrappers = this.cachedWrapperClasses;
}
// 緩存clazz
wrappers.add(clazz);
} catch (NoSuchMethodException var27) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = this.findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() <= this.type.getSimpleName().length() || !clazz.getSimpleName().endsWith(this.type.getSimpleName())) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - this.type.getSimpleName().length()).toLowerCase();
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = (Activate)clazz.getAnnotation(Activate.class);
if (activate != null) {
this.cachedActivates.put(names[0], activate);
}
String[] arr$ = names;
int len$ = names.length;
for(int i$ = 0; i$ < len$; ++i$) {
String n = arr$[i$];
if (!this.cachedNames.containsKey(clazz)) {
this.cachedNames.put(clazz, n);
}
Class<?> c = (Class)extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + this.type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable var28) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + this.type + ", class line: " + line + ") in " + url + ", cause: " + var28.getMessage(), var28);
this.exceptions.put(line, e);
}
}
} finally {
reader.close();
}
} catch (Throwable var30) {
logger.error("Exception when load extension class(interface: " + this.type + ", class file: " + url + ") in " + url, var30);
}
}
}
} catch (Throwable var31) {
logger.error("Exception when load extension class(interface: " + this.type + ", description file: " + fileName + ").", var31);
}
}
loadExtensionClasses()的基本流程:
1.SPI註解解析
2.調用 loadDirectory 方法加載指定文件夾配置文件。
3.loadResource 方法用於讀取和解析配置文件,並通過反射加載類;
4.緩存相關信息
5.Dubbo-IOC源碼
private T createExtension(String name)方法在創建完實例後,執行// IOC注入屬性 this.injectExtension(instance);
我們進入源碼分析
private T injectExtension(T instance) {
try {
if (this.objectFactory != null) {
// 獲取所有方法
Method[] arr$ = instance.getClass().getMethods();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
// 如果方法爲set方法,且級別爲public
Method method = arr$[i$];
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 = this.objectFactory.getExtension(pt, property);
// 通過反射賦值
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception var9) {
logger.error("fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
}
}
}
}
} catch (Exception var10) {
logger.error(var10.getMessage(), var10);
}
return instance;
}
在上面代碼中,objectFactory 變量的類型爲 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其他類型的 ExtensionFactory。Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。前者用於創建自適應的拓展,後者是用於從 Spring 的 IOC 容器中獲取所需的拓展。
Dubbo-IOC目前主要是set方法,很簡單;
6.總結
- 生成接口類的ExtensionLoader對象:
- 參數判空,接口類型校驗,@SPI註解校驗
- 生成ExtensionLoader,objectFactory;
- 根據ExtensionLoader.getExtensionName(name)獲取擴展節點實例;
- 查詢緩存的handler,沒有則生成handler;查詢handler的值,爲空則createExtension(name)創建對應實例;
- 根據配置,底層根據class.forName生成class對象實例,返回class對象
- 調用class.newInstance創建實例,初始化,IOC賦值等;
- 根據生成的實例,進行面向接口編程;