Dubbo的SPI
SPI什麼是SPI,SPI全稱爲Service Provider Interface,是一種服務發現機制,SPI的本質是將接口實現類的全限定名配置到文件中,並由服務器加載讀取配置文件,加載實現類,這樣可以在運行時,動態爲接口替換實現類,正因此特性,我們可以很容易的通過SPI機制爲我們的程序提供擴展功能,SPI機制在第三方框架中也有所應用,比如 Dubbo 就是通過 SPI 機制加載所有的組件。不過,Dubbo 並未使用 Java 原生的SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重的模塊。
1、Dubbo SPI
先看一下java原生的SPI,用到了反射機制,跟Spring的IOC裏的一樣,通過全限定類名,在程序運行階段,在內存中創建對象 newInstance。
創建一個接口類和兩個實現類
public interface User {
public void service();
}
public class UserOneImpl implements User {
public void service(){
System.out.println("實現類一");
}
}
public class UserTwoImpl implements User {
public void service(){
System.out.println("實現類二");
}
}
在resources目錄下創建一個目錄,META-INF.services
在這個目錄下把,接口的全限定類名
com.jd.service.User ,裏面配置內容是接口實現類的全限定類名
com.jd.service.impl.UserOneImpl
com.jd.service.impl.UserTwoImpl
然後編寫一個測試類
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<User> load = ServiceLoader.load(User.class);
//使用了反射機制,創建了對象。
Iterator<User> iterator = load.iterator();
while (iterator.hasNext()){
User userImpl = iterator.next();
userImpl.service();
}
}
}
控制檯輸出了 :實現類一 實現類二
原生SPI源碼分析原理,SerivceLoader進入這個類,可以看到成員變量寫好了路徑,然後調用load方法
private static final String PREFIX = "META-INF/services/";
進入load.iterator(),重寫了hasNext方法
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
進入hasNext方法
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
進入hasNextService方法
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
存到pending中,pending賦值給了nextName。
接下來回到iterator()方法中,繼續走next方法,進入lookupIterator中的next方法
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
然後進入nextService()方法中。把之前的nextName值付給了cn,cn通過反射,取消類名c,類c 被實例化newInstance(),賦值給了p,然後返回p。p就是實例化對象,然後我們用接口進行接收的。這個就是Java原生的SPI的一個源碼分析。
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
這就是java的原生SPI。有一些缺點:
接口的所有實現類全部都需要加載並實例化
無法根據參數來獲取所對應的實現類
不能解決IOC、AOP的問題。基於以上問題,dubbo在原生SPI機制進行了增強,解決以上問題。
Dubbo的SPI機制,Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。Dubbo SPI 所需的配置文件需放置在META-INF/dubbo 路徑下,配置內容如下,源碼
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
test1=com.jd.service.impl.UserOneImpl
test2=com.jd.service.impl.UserTwoImpl
先引入座標依賴,
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
</dependency>
在接口上加一個註解@SPI
重新編寫一個測試類
public class DubboSpi {
public static void main(String[] args) {
ExtensionLoader<User> extensionLoader = ExtensionLoader.getExtensionLoader(User.class);
User userImpl = extensionLoader.getExtension("test1");
userImpl.service();
}
}
控制檯輸出 ,實現類一,因爲只配了key值是test1
源碼分析,首先進入的是getExtensionLoader方法,通過 ExtensionLoader 的 getExtensionLoader 方法獲取一ExtensionLoader 實例,該方法方法先從緩存中獲取與拓展類對應的 ExtensionLoader,若緩存未命中,則創建一個新的實例
else {
//從緩存中獲取,如果緩存中有直接返回loader,如果緩存中沒有,重新創建一個loader對象
ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if(loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
接下來看getExtension方法
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//先從緩存中取,如果緩存中沒有就新建一個holder對象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
//然後調用holder中的get方法,創建instance對象,如果是新建holder對象,get出來的是空的,如果是從緩存中取的get出來的不是空的。
Object instance = holder.get();
//如果是空的,使用createExtension創建實例,instance代表的就是我們那個接口的實例對象。
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
接下來進入createExtension方法中
private T createExtension(String name) {
//1、根據name值就是key值,通過getExtensionClasses()獲取所有的拓展類,如果等於null就報異常
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//也是,從緩存中取拿,如果緩存沒有,就創建一個
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//2、通過反射創建拓展對象實例
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//3、需要依賴注入,向實例中注入依賴
//此處injectExtension方法依賴注入實現原理:Dubbo 首先會通過反射獲取到實例的所
// 有方法,然後再遍歷方法列表,檢測方法名是否具有 setter 方法特徵。若有,則通過
//ObjectFactory 獲取依賴對象,最後通過反射調用 setter 方法將依賴設置到目標對象
// 中。
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
//4、將拓展對象包裹在相應的 Wrapper 對象中循環創建 Wrapper 實例
for (Class<?> wrapperClass : wrapperClasses) {
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);
}
}
Dubbo的服務暴露
Dubbo的服務引入