JDK SPI和Dubbo SPI

SPI,全稱為(Service Provider Interface),是JDK內置的一種服務提供機制。

一、JDK SPI實現

約定規範:

1.配置文件必須在指定文件夾 META-INF/services/ 內。

2.配置文件編碼格式必須為:UTF-8。

3.配置文件名必須是 **類的全限定名**。

4.接口如果有多個實現,在配置文件中以 # 作為分隔符。

<code>JDK SPI源碼:
// 重載函數, 默認為 當前線程的 ContextClassLoader

public static ServiceLoader load(Class service) {

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

//返回 ServiceLoader 實例

public static ServiceLoader load(Class service, ClassLoader loader) {

return new ServiceLoader<>(service, loader);

}
/<code>

示例:

一個SPI的實現類,比如實現UserService接口為例。

需要SPI擴展接口

io.service.UserService

需要實現類

io.service.impl.UserServiceImpl

加載流程:

以 java.util.ServiceLoader 類的 load 方法作為入口

<code>ServiceLoader<userservice> serviceLoader =  ServiceLoader.load(UserService.class);/<userservice>/<code>


0.在 resource 文件夾下創建子文件夾 META-INF.services,並新增名為: io.service.UserService 的文件,文件內容如下所示:

io.service.impl.UserServiceImpl

1.獲取META-INF/services/目錄下文件

<code>```
private Iterator<string> parse(Class> service, URL u)
throws ServiceConfigurationError{}
```/<string>/<code>

其中service 表示需要加載的類對象, u 表示 META-INF.services 下文件 在系統中的全路徑。


2.解析文件

根據#分割成多個文件,寫入List<string>集合/<string>


3.上面集合迭代實現加載(public final class ServiceLoader implements Iterable),然後通過反射,cn為文件全路徑名,當前線程的ClassLoader

<code>```
//通過反射,獲取目標類
c = Class.forName(cn, false, loader);
```/<code>


4.目標類轉換成相應類

<code>```
// 目標類實例轉換為響應的類,並將其加載到 providers 列表中
S p = service.cast(c.newInstance());
providers.put(cn, p);
```/<code>

寫入將ServiceLoader類的屬性provides,底層是LinkedHashMap,保證調用順序

<code>```
// Cached providers, in instantiation order
private LinkedHashMap<string> providers = new LinkedHashMap<>();
```/<string>/<code>

5.使用 Iterator 接口的實現類LazyIterator,將服務加載的這個動作延遲到使用服務時。這樣做的好處是:**只有在真正使用時才加載,避免造成沒必要的加載。但未解決,未使用擴展加載的問題**。

<code>// 用於延遲加載接口的實現類
private LazyIterator lookupIterator;/<code>

缺點:

雖然使用延遲加載,但是隻能通過遍歷全部獲取,接口的實現類全部加載並實例化一遍。如果不想用某些實現類,它也被加載並實例化,這就造成了資源浪費。

二、Dubbo SPI擴展機制

Dubbo對JDK SPI進行了擴展,對服務提供者配置文件中的內容進行了改造,由原來的提供者類的全限定名列表改成了KV形式的列表,這也導致了Dubbo中無法直接使用JDK ServiceLoader。

Dubbo默認依次掃描META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/三個classpath目錄下的配置文件。配置文件以具體擴展接口全名命名

JDK SPI和Dubbo SPI

dubbo spi

JDK SPI和Dubbo SPI

dubbo spi

流程:

1.dubbo核心實現類ExtensionLoader,通過接口類名和key值獲取一個實現類。

2.通過Adaptive實現,生成一個代理類,根據實際調用時,先靜態,後動態決定要調用的類。

3.採用裝飾器模式進行功能增強,自動包裝實現,這種實現的類一般是自動激活的。

JDK SPI和Dubbo SPI

ExtensionLoader總體流程

源碼解析:

1首先加載實例:

ExtensionLoader充當插件工廠角色,提供了一個私有的構造器。其入參type為擴展接口類型。Dubbo通過SPI註解定義了可擴展的接口,如Filter、Transporter等。每個類型的擴展對應一個ExtensionLoader。SPI的value參數決定了默認的擴展實現

當擴展類型是ExtensionFactory時,不指定objectFactory,否則初始化ExtensionFactory的ExtensionLoader並獲取一個擴展適配器

<code>
@SPI("dubbo")
public interface Protocol {

/**
* 獲取缺省端口,當用戶沒有配置端口時使用。
*
* @return 缺省端口
*/
int getDefaultPort();

/**
* 暴露遠程服務:
* 1. 協議在接收請求時,應記錄請求來源方地址信息:RpcContext.getContext().setRemoteAddress();

* 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。


* 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。

*
* @param invoker 服務的執行體
*/
@Adaptive
Exporter export(Invoker invoker) throws RpcException;

/**
* 引用遠程服務:
* 1. 當用戶調用refer()所返回的Invoker對象的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker對象的invoke()方法。

* 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中發送遠程請求。

* 3. 當url中有設置check=false時,連接失敗不能拋出異常,並內部自動恢復。

*
* @param type 服務的類型
* @param url 遠程服務的URL地址
*/
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;

/**
* 釋放協議:

*/
void destroy();

}


public class DubboProtocol extends AbstractProtocol {

public static final String NAME = "dubbo";
...
...
}

// 示例:
ExtensionLoader<protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
// 根據Key獲取相應的擴展實現類實例
Protocol dubboProtocol = protocolLoader.getExtension(DubboProtocol.NAME);/<protocol>
/<code>

其次,getExtension是實現整個擴展加載器ExtensionLoader中最核心的方法。分為4步,

1.1 讀取配置文件,並緩存;

<code>     // 1. 先從緩存中取相應的擴展實現類實例
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) {
// 創建相應的擴展實現類實例,並緩存
instance = createExtension(name);
holder.set(instance);
}
}
}/<object>/<object>/<code>

1.2 根據傳入的名稱初始化擴展類;

<code>     // 2. 根據name獲取相應擴展類的類實例
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);
}
\t //注入依賴,即IOC
injectExtension(instance);
Set<class>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class> wrapperClass : wrapperClasses) {
\t //通過反射創建Wrapper實例,向Wrapper實例注入依賴,最後賦值給instance,自動包裝實現類似aop功能
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
\t\t//返回擴展類示例
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}/<class>/<code>

1.3 嘗試查找符合條件的包裝類,符合條件的注入擴展類(反射實現的,查詢set開頭的方法),並注入類的實例;

<code>private T injectExtension(T instance) {
try {
if (this.objectFactory != null) {
Method[] var2 = instance.getClass().getMethods();
\tint var3 = var2.length;
\tfor(int var4 = 0; var4 < var3; ++var4) {

Method method = var2[var4];
if (this.isSetter(method) && method.getAnnotation(DisableInject.class) == null) {
Class> pt = method.getParameterTypes()[0];
\tif (!ReflectUtils.isPrimitives(pt)) {
try {
String property = this.getSetterProperty(method);
\tObject object = this.objectFactory.getExtension(pt, property);
\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (object != null) {
// 注入類的實例
method.invoke(instance, object);
}
} catch (Exception var9) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + this.type.getName() + ": "
+ var9.getMessage(), var9);
}
}
}
}
}
} catch (Exception var10) {
logger.error(var10.getMessage(), var10); }

return instance;
}/<code>

1.4 返回擴展類實例。

2.自適應擴展器

dubbo提供了兩種方式來生成擴展適配器:靜態適配器擴展和動態適配器擴展。

靜態適配器擴展,就是提前通過編碼的形式確定擴展的具體實現,且該實現類由Adaptive註解標註,如:AdaptiveCompiler。在加載配置文件的loadFile方法中,已經描述過處理該類型擴展的邏輯,具體可參考loadFile()方法源碼。

動態適配器擴展,

即通過動態代理生成擴展類的動態代理類,在dubbo中是通過javassist技術生成的。與傳統的jdk動態代理、cglib不同,javassist提供封裝後的API對字節碼進行間接操作,簡單易用,不關心具體字節碼,靈活性更高,且處理效率也較高,是dubbo默認的編譯器。

2.1 首先,從ExtensionLoader構造器中會調用getAdaptiveExtension()方法觸發為當前擴展類型生成適配器:

<code>private ExtensionLoader(Class> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ?
null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class)
.getAdaptiveExtension());
}

public T getAdaptiveExtension() {
// 1. 首先,檢查是否存在當前擴展類靜態適配器
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 2. 創建當前擴展類動態適配器
instance = createAdaptiveExtension();
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;
}/<code>

2.2 創建動態類擴展適配器

<code> private T createAdaptiveExtension() {
try {
// IOC屬性注入
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}/<code>

2.3 根據adaptive註解的value數組值,及SPI註解定義的默認擴展名,確定適配邏輯,即擴展獲取的優先級,以下代碼為例其獲取優先級是server,transporter,netty

<code>package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter {
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("client", url.getParameter("transporter", "netty")); // 處理順序
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty")); // 處理順序
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}/<code>

2.4 拿到擴展名後,再從ExtensionLoader獲取到擴展實例,調用具體的bind方法。


源碼生成後,ExtensionLoader再調用默認的JavassitCompiler進行編譯和類加載。


3.1 dubbo中存在一種對於擴展的封裝類,其功能是將各擴展實例串聯起來,形成擴展鏈,比如過濾器鏈,監聽鏈。當調用ExtensionLoader的getExtension方法時,會做攔截處理,如果存在封裝器,則返回封裝器實現,而將真實實現通過構造方法注入到封裝器中

<code>        Set<class>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}/<class>/<code>


分享到:


相關文章: