一點一點實現一個RPC框架三 -- 學習dubbo spi

前言

瞭解過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的邏輯還是比較簡單的, 代碼細節有興趣可以跟一下. 真的是時間拖得越長, 文章越水. 水完這篇還有三篇~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章