Dubbo 源碼分析 - Dubbo SPI 經過 getExtension 獲取擴展點實現類對象

開篇

前面用了4 篇文章分析了 Dubbo SPI 的幾種用法以及如何在 Dubbo 中應用的,apache

本文經過調試 Dubbo2.7.x 源碼分析 如何經過 getExtension(name) 獲取一個擴展對象實例緩存

正文

回顧一下 Dubbo SPI 的最基本的用法app

public class App 
{
    public static void main( String[] args )
    {
        // 第一步
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        // 第二步
        HelloService helloService = extensionLoader.getExtension("helloService");
        // 第三步
        helloService.sayHello("xiaoming");
    }
}

咱們直接從第二步 debug 進入getExtension(name)方法源碼分析

1. ExtensionLoader 的屬性

在 debug 進入方法以前, 先來看幾個 ExtensionLoader的屬性.net

// 1. 擴展接口, 好比 Protocol
    private final Class<?> type;
    
    // 2. 擴展實現類集合, key 爲 Protocol , value 爲 DubboProtocol
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    
    // 3. (緩存的)擴展實現類集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 4. 緩存的擴展對象集合 key 爲 dubbo, value 爲 DubboProtocol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
 
    // 5. 擴展加強 Wrapper 實現類集合
    private Set<Class<?>> cachedWrapperClasses;

2. ExtensionLoader # getExtension(String name)

此方法大體分爲 3 大步:debug

  • 若是 name 爲 "true", 則獲取 默認的擴展類對象
  • 不然, 就去取 緩存的擴展類對象
  • 若是緩存中不存在,就去加載並 實例化擴展類 , 並放入緩存

下面就針對上面的 三大步, 逐個分析調試

public T getExtension(String name) {
        
        // 省略擴展名非空校驗     
        
        // 1. 若是 name 等於 true, 獲取默認的擴展對象
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        
        // 2. 從緩存的擴展類對象獲取  
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        
        // 緩存中沒有對應的實例
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                // 雙重校驗
                if (instance == null) {
                    // 3. 加載擴展實現類,並實例化
                    instance = createExtension(name);
                    // 擴展對象放入緩存
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

2.1. ExtensionLoader # getDefaultExtension()

public T getDefaultExtension() {
        // 1.1 獲取全部的擴展類
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
    }
2.1.1 getExtensionClasses()
private Map<String, Class<?>> getExtensionClasses() {
        // 先從緩存中取
        // cachedClasses 放置的是緩存的擴展實現類集合
        Map<String, Class<?>> classes = cachedClasses.get();
        // 依然是雙重校驗+鎖的方法去獲取
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 緩存沒有命中, 就會去加載 META-INF/dubbo ,META-INF/dubbo/intenal,   ,META-INF/service 目錄下去加載
                    classes = loadExtensionClasses();
                    // 加載完成,就放入緩存
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
2.1.2 loadExtensionClasses()

上面也有提到, 緩存中沒有,就會去下面的目錄去加載文件, 而後解析文件中的內容code

  • META-INF/dubbo
  • META-INF/dubbo/intenal
  • META-INF/service

具體源碼就不貼出來了, 文件的內容格式以下:對象

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

2.2. ExtensionLoader # getOrCreateHolder()

private Holder<Object> getOrCreateHolder(String name) {
        // 跟上面一模一樣, 先從緩存中取,    
        // cachedInstances 放置的是 緩存的擴展類對象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

2.3 ExtensionLoader # createExtension(String name)

  • 根據名稱(好比: dubbo)獲取擴展實現類(DubboProtocol)
  • 從擴展實現類集合中獲取擴展實現類對象 instance
  • 若是擴展類對象 instance依賴其餘擴展實現類 OtherClass, 就須要把 OtherClass 實例化,並經過 setter 方法注入到instance裏面
  • 判斷 Protocol 是否有其餘加強實現, 好比 ProtocolFilterWrapper等等blog

    • 若是有, 則把它 ProtocolFilterWrapper實例化, 賦值給 instance
private T createExtension(String 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, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 注入依賴的擴展類對象
            injectExtension(instance);
            // 判斷是不是 wrapper
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 從新賦值成一個 wrapper (加強類)
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

總結

本文主要針對經過 getExtension(name)獲取一個擴展對象實例, 來對 Dubbo 的源碼進行了剖析, 固然全文只是描述了一個大概的流程, 好比如何解析 SPI 配置文件 就沒有深刻去講解.

大致流程以下所示:

  • 若是是默認的擴展對象( "true".equals(name)), 經過 getDefaultExtension()方法獲取 默認的擴展接口實現類對象, 並返回
  • 判斷緩存 cachedInstances集合中是否存在

    • 若是有, 就從緩存的擴展接口實現類對象獲取, 賦值給 instance
    • 若是沒有, 就經過 createExtension獲取 擴展接口實現類對象

      • 獲取擴展實現類 好比 DubboProtocol
      • 判斷緩存中是否有擴展接口實現類的對象 instance, 若是沒有就把上一步的類DubboProtocol給實例化
      • 判斷instance是否依賴其餘擴展接口實現類對象 CLassA, CLassB 等等,若是有,須要經過 setter 方法注入進去
      • 判斷 DubboProtocol是否有其餘增 Wrapper 實現類, 好比 ProtocolFilterWrapper, 若是有, 賦值給上面的instance
      • 最後放入緩存
  • 返回擴展實現類對象 return instance

下一篇文章會針對 獲取自適應擴展點實例 getAdaptiveExtension()方法進行源碼分析!

相關文章
相關標籤/搜索