前言
瞭解過java spi後, 馬上來看看dubbo spi有什麼特殊之處
dubbo spi
demo
還是以一個運行的demo開始, 基本和官網一樣
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot r = extensionLoader.getExtension("r");
r.sayHello();
Robot t = extensionLoader.getExtension("t");
t.sayHello();
}
// 暴露的接口
@SPI
public interface Robot {
void sayHello();
}
// 實現類
public class R2Robot implements Robot {
@Override
public void sayHello() {
System.out.println("r2");
}
}
public class T1Robot implements Robot {
@Override
public void sayHello() {
System.out.println("T1");
}
}
對比下前面的java spi的demo, 感覺套路差不多, 都是通Class創建一個加載器, 通過這個加載器獲取到需要的實現類. 唯一的不同就是, java spi是通過迭代器獲取到所有的實現類, 然後由用戶再次篩選. 而dubbo spi可以直接通過一個key獲取到對應的實現類. 現在來看下dubbo是如何實現的.
ExtensionLoader
先看註釋, 再看屬性
* Load dubbo extensions
* <ul>
* <li>auto inject dependency extension </li>
* <li>auto wrap extension in wrapper </li>
* <li>default extension is an adaptive instance</li>
* </ul>
額...有點短啊~ 翻譯下 加載dubbo擴展
- 自動加載依賴的擴展
- 包裝器自動包裝擴展
- 默認擴展是一個adaptive實例
屬性如下, 有點多啊~ 基本都是一些緩存信息, 有註釋的都是在demo運行中使用到的
// 三個路徑, 都可以加載擴展
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/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
// 緩存ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
// 擴展實體緩存, 實例通過反射創建class.newInstance()
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
// ==============================
// 接口類型
private final Class<?> type;
// 緩存加載的類信息, key是配置名稱
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// 緩存實例對象, key是配置的對象名稱
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
通過註釋介紹和擁有的屬性, 我們大概知道ExtensionLoader負責從文件中加載擴展, 並構造可運行實例, 當然在這過程中會緩存類信息, 實例信息等, 方便後面再次獲取. 下面來跟蹤下源碼, 看看到底是如何實現的. 兩個限制條件: 是否爲interface; 是否spi註解
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
// 源碼
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// 必須是interface
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 必須是SPI註解修飾
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 先從緩存中獲取
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;
}
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
so easy~ loader有了接下來就是創建對象, 創建對象的過程也很簡單, 1 從緩存中獲取, 並返回 2 沒有, 則從配置文件中擴展 3 實例化對象 4 注入依賴 5 返回對象 代碼比較簡單就不全貼了. 看下依賴注入這裏, 這裏的注入就是通過set方法將依賴的擴展注入進來.
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
// 判斷是否爲set方法, 也就是說dubbo目前只支持set方法注入
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
// 如果參數是基礎類型就不注入, 因爲這裏注入指的是將加載的對象注入進來
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
總結
dubbo spi的邏輯還是比較簡單的, 代碼細節有興趣可以跟一下. 真的是時間拖得越長, 文章越水. 水完這篇還有三篇~