Dubbo原始碼解讀-Dubbo的容器啟動

掘金小部隊發表於2018-03-15

一直信奉世間萬事萬物的執行都有各自的規則。程式設計也有程式設計的原則,Linux有Linux的設計原則,Spring的設計原則是IoC和AOP。因此在閱讀一個框架原始碼時要抓住這個框架設計的原則,這樣才能容易理解。

Dubbo是阿里巴巴公司開源的一個分散式服務框架,主要功能有:高效能NIO通訊及多協議整合,服務動態定址與路由,軟負載均衡與容錯,依賴分析與降級等。

image_1c8f3oihumdtu9giqgndmoqq9.png-74.5kB

  • Container 服務執行容器
  • Provider 暴露服務的服務提供方
  • Registry 服務註冊與發現的註冊中心
  • Consumer 呼叫遠端服務的服務消費方
  • Monitor 統計服務的呼叫次數和呼叫時間的監控中心

我們按Dubbo架構的模組各個擊破。

  • Container Dubbo服務的啟動都是從啟動容器開始的,Dubbo服務的啟動有三種方法: 1.使用Servlet容器執行; 2.自建Main方法執行(Spring容器); 3.使用Dubbo提供的Main方法類執行(Spring容器);

一般建議使用Dubbo提供的Main方法類執行,能夠優雅關機。Main方法類裡也是啟動一個容器,這裡就設計到SPI擴充套件機制了。首先我們們說說是SPI擴充套件機制。SPI(Service Provider Interface)是JDK內建的一直服務發現機制。比如JDBC中就是通過SPI機制提供給各資料庫廠商呼叫實現介面。其實就是提供一個介面,在執行時動態新增實現。 下面我們說說Dubbo中怎麼實現SPI的。在com.alibaba.dubbo.container.Main類中啟動main方法。我會在程式碼註釋中打上標識“① ②“可以根據標識定位檢視具體的原始碼解析。

//com.alibaba.dubbo.container.Main
public class Main {

    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";

    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    //建立ExtensionLoader物件,SPI擴充套件機制的實現就在這個類裡。①解析ExtensionLoader.getExtensionLoader
    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);

    private static final ReentrantLock LOCK = new ReentrantLock();

    private static final Condition STOP = LOCK.newCondition();

    public static void main(String[] args) {
        try {
            //檢視是否有入參
            if (args == null || args.length == 0) {
                //沒有的話就通過ConfigUtils.getProperty()方法去獲取預設定的config②解析ExtensionLoader.getDefaultExtensionName 
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }

            final List<Container> containers = new ArrayList<Container>();
            for (int i = 0; i < args.length; i++) {
              //通過上面獲取的args從loader中獲取上面已經載入的容器物件。比如這裡是通過“spring”獲取到容器物件SpringContainer
               containers.add(loader.getExtension(args[i]));
            }
            logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");

            if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        for (Container container : containers) {
                            try {
                                container.stop();
                                logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
                            } catch (Throwable t) {
                                logger.error(t.getMessage(), t);
                            }
                            try {
                                LOCK.lock();
                                STOP.signal();
                            } finally {
                                LOCK.unlock();
                            }
                        }
                    }
                });
            }

            for (Container container : containers) {
                //啟動容器,這裡就是spring容器
                container.start();
                logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
            }
            System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
        } catch (RuntimeException e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            System.exit(1);
        }
        try {
            LOCK.lock();
            STOP.await();
        } catch (InterruptedException e) {
            logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
        } finally {
            LOCK.unlock();
        }
    }

}
複製程式碼

①解析ExtensionLoader.getExtensionLoader Main類中初始化了一個ExtensionLoader物件,這個物件是通過 ExtensionLoader.getExtensionLoader(Container.class)初始化的。SPI機制的實現就在ExtensionLoader中體現的。

//com.alibaba.dubbo.common.extension.ExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //不能為空
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //type要是一個介面
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //type上需要有SPI註解,比如Container上就有註解@SPI("spring"),有了這個註解才能實現SPI機制動態實現
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        //EXTENSION_LOADERS是一個ConcurrentHashMap<Class<?>, ExtensionLoader<?>>()
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //建立一個type的ExtensionLoader
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
複製程式碼

②解析ExtensionLoader.getDefaultExtensionName

//獲取預設的副檔名
public String getDefaultExtensionName() {
        getExtensionClasses();
        //cachedDefaultName是個全域性變數,在loadExtensionClasses方法中賦值
        return cachedDefaultName;
    }
    
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //載入擴充套件類
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
    
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
        //獲取預設的註解,這裡是@SPI("spring")
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //獲取註解值這裡是“spring”
            String value = defaultAnnotation.value();
            if (value != null && (value = value.trim()).length() > 0) {
                //正則匹配註解值,通過“,”分隔符
                String[] names = NAME_SEPARATOR.split(value);
                //如果註解的欄位大於1,就丟擲異常,也就是說@SPI註解中只可以有一個值
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                //給cacheDefaultName賦值,這裡賦值為“spring”
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        //載入不同路徑下的類放到extensionClasses,具體細節可以看loadFile方法
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
複製程式碼

image_1c8k4anda1a941nbrgegp2f1hgcm.png-47kB
META-INF.dubbo.internal下有個配置檔案,裡面的配置是

spring=com.alibaba.dubbo.container.spring.SpringContainer
複製程式碼

因此可以通過"spring"擴充套件實現的類是com.alibaba.dubbo.container.spring.SpringContainer。

這裡只是簡單分析Dubbo的一種啟動方式,不是很深入,拋磚引玉,大家可以繼續深入的研究一下。

相關文章