1. 程式人生 > >Dubbo/Dubbox的服務暴露(一)

Dubbo/Dubbox的服務暴露(一)

前言

原始碼入手

平時我要了解一個框架,基本會去從他的Listener入手,如果web.xml中沒有配置listener可能還會有 filter,這是spring給我們的啟示,可是當要去了解dubbo的時候,發現dubbo並沒有自己的listener監聽器。已知dubbo是一款和spring結合較好的rpc框架,那麼其不使用web容器相關的方式,必然遵循spring的方式。依據平時開發經驗,我們知道要想在Spring初始化之後,做一些自己的邏輯,有一種方法即實現org.springframework.beans.factory.InitializingBean介面,那麼我們搜一下,dubbo有沒有這麼幹。
這裡寫圖片描述

前文 Dubbo 對配置檔案的解析我們知道,服務(beans->dubbo:service)相關的節點配置會在com.alibaba.dubbo.config.spring.ServiceBean的例項中儲存,這裡正好巧了public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
出去一些初始化引數的工作,這裡我們主要關注這裡

public void onApplicationEvent(ApplicationEvent event) {
        if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
            if (isDelay() && ! isExported() && ! isUnexported()) {
                if (logger.isInfoEnabled()) {
                    logger.info("The service ready on spring started. service: "
+ getInterface()); } //暴露服務 export(); } } }

public synchronized void export() 方法裡會做一些判斷,我們這裡只關注自己最關注的部分

public synchronized void export() {
        //.......如果當前服務被暴露過就不再暴露,等一些判斷
        if (delay != null && delay > 0) {//delay 延遲暴露引數,如果配置延遲暴露。
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(delay);
                    } catch (Throwable e) {
                    }
                    doExport();
                }
            });
            thread.setDaemon(true);
            thread.setName("DelayExportServiceThread");
            thread.start();
        } else {
            doExport();
        }
    }

很尷尬,好多原始碼都沒有相關的文件註釋,如果沒參考相關資料,第一次閱讀會很難受
protected synchronized void doExport() 這個方法的作用大概是執行具體的服務暴露

 protected synchronized void doExport() {
        //......異常處理省略.......//
        //檢查provider是否有配置,如果該屬性為null,則new一個ProviderConfig物件,方法內部呼叫appendProperties(provider)方法,該方法內部會拼裝一個 dubbo.tagName.屬性名的key,在配置檔案中查詢值,如果有值則呼叫屬性的setter方法,設定屬性值。
        checkDefault();
        //如果ProviderConfig例項不為null的情況下,初始化一些配置,通常情況下很少會配置<dubbo:provider/>標籤
        //提供方的預設值,當ProtocolConfig和ServiceConfig某屬性沒有配置時,採用此預設值,可選。
        if (provider != null) {
            //application 對應 <dubbo:application/> 應用配置,用於配置當前應用資訊,不管該應用是提供者還是消費者。
            if (application == null) {
                application = provider.getApplication();
            }
            // module 對應<dubbo:module/>模組配置,用於配置當前模組資訊,可選。
            if (module == null) {
                module = provider.getModule();
            }
            //registries 對應 <dubbo:registry/> 註冊中心配置,用於配置連線註冊中心相關資訊。
            if (registries == null) {
                registries = provider.getRegistries();
            }
            // monitor 對應 <dubbo:monitor/> 監控中心配置,用於配置連線監控中心相關資訊,可選。
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            //protocols 對應 <dubbo:protocol/> 協議配置,用於配置提供服務的協議資訊,協議由提供方指定,消費方被動接受。
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        //........省略進一步初始化相關配置的程式碼行 .......//
        if (ref instanceof GenericService) { //判斷是否是泛化呼叫介面,這裡不重要。
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {

                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //檢查介面類中是否存在指定的方法,如果dubbo:service->dubbo:method 沒有配置的情況下,methods為null,該方法不會執行方法校驗。如果有相關的配置,該方法會檢查name屬性對應的方法是否存在,不存在會拋IllegalStateException異常。
            checkInterfaceAndMethods(interfaceClass, methods);
            // 檢查服務實現,如果ref不是服務的介面實現,則會丟擲IllegalStateException異常。
            checkRef();
            // 宣告該介面非泛化呼叫
            generic = Boolean.FALSE.toString();
        }
        if(local !=null){ //如果是本地服務,常用配置方案下,local 值為null
            if(local=="true"){
                local=interfaceName+"Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if(!interfaceClass.isAssignableFrom(localClass)){
                throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if(stub !=null){//如果是遠端服務,常用配置方案下,stub值為null
            if(stub=="true"){
                stub=interfaceName+"Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if(!interfaceClass.isAssignableFrom(stubClass)){
                throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        //檢查應用配置,如果沒有配置會建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。檢查失敗會丟擲IllegalStateException異常
        checkApplication();
        //檢查註冊中心配置,如果沒有配置,則會檢查配置檔案中的dubbo.registry.address 設定,並建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。檢查失敗會丟擲IllegalStateException異常
        checkRegistry();
        //檢查協議配置,該方法不會丟擲異常,如果沒配置dubbo:protocol,會取dubbo:provider相關配置填充,如果依舊protocols依舊為null,則建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。
        checkProtocol();
        //填充屬性 不多說
        appendProperties(this);
        //
        checkStubAndMock(interfaceClass);
        // 初始化path屬性
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 暴露服務的URL
        doExportUrls();
    }

doExportUrls(); 實現如下(吐槽,懷疑下到假的原始碼了,到處都沒有文件註釋):

 @SuppressWarnings({ "unchecked", "rawtypes" })
    private void doExportUrls() {
        //獲得註冊中心列表
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

protected List loadRegistries(boolean provider) 類相關解析

protected List<URL> loadRegistries(boolean provider) {
        // 檢查註冊中心
        checkRegistry();
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && registries.size() > 0) {
            for (RegistryConfig config : registries) {
            //一般這個拿到的是註冊中心地址,例如:zookeeper://1.6.5.5:2181
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                    address = Constants.ANYHOST_VALUE;
                }

                String sysaddress = System.getProperty("dubbo.registry.address");
                if (sysaddress != null && sysaddress.length() > 0) {
                    address = sysaddress;
                }
                if (address != null && address.length() > 0 
                        && ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    appendParameters(map, application);
                    appendParameters(map, config);
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getVersion());
                    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                    if (ConfigUtils.getPid() > 0) {
                        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                    }
                    if (! map.containsKey("protocol")) {
                        //查詢註冊中心是否支援remote協議
                        if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                            map.put("protocol", "remote");
                        } else {
                            map.put("protocol", "dubbo");
                        }
                    }
                    //目前到這裡是zookeeper://ip地址:2181/com.alibaba.dubbo.registry.RegistryService?application=配置應用名&dubbo=2.8.4&file=快取檔案路徑&logger=log4j&organization=22222&owner=weiythi&pid=20772&timestamp=1510040471140
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {
                        url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                        //這個url會被轉換成這種格式 registry://ip:2181/com.alibaba.dubbo.registry.RegistryService?application=配置的應用名&dubbo=2.8.4&file=快取檔案路徑&logger=log4j&organization=22222&owner=weiythi&pid=20772&registry=zookeeper&timestamp=1510040471140 即會將註冊中心轉換成registry引數放到url中,並將url的protocol設定成registry
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                                || (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        //獲得協議資訊,預設dubbo
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }

        String host = protocolConfig.getHost();
        if (provider != null && (host == null || host.length() == 0)) {
            host = provider.getHost();
        }
        boolean anyhost = false;
        //判斷host是不是為null,或localhost,等本地ip地址
        if (NetUtils.isInvalidLocalHost(host)) {
            //......省略 使用各種方式獲取本機的實際ip地址........//
            host = InetAddress.getLocalHost().getHostAddress();
            //......省略 使用各種方式獲取本機的實際ip地址........//
        }


        Integer port = protocolConfig.getPort();
        //.....省略,如果port為null,則嘗試使用預設的port,如果預設的port無法使用,則會嘗試使用隨機的埠
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) {
            map.put(Constants.ANYHOST_KEY, "true");
        }
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
         // appendParameters 其實就是把屬性值轉到map中,這裡不做說明
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        // 常用配置方案下,methods為null,上文程式碼註釋中有說明
        if (methods != null && methods.size() > 0) {
            //......省略,當有暴露指定方法的配置時,會暴露指定方法
        }
        if (ProtocolUtils.isGeneric(generic)) {
            map.put("generic", generic);
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
            //獲取介面中所有的方法名
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                //拼接方法名為 a,b,c,d 這種形式
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        if (! ConfigUtils.isEmpty(token)) { //處理token
            if (ConfigUtils.isDefault(token)) {
                map.put("token", UUID.randomUUID().toString());
            } else {
                map.put("token", token);
            }
        }
        if ("injvm".equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // 匯出服務
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
        // 注意這裡的URL是com.alibaba.dubbo.common.URL 物件,最後會被處理成一個類似這樣的格式,以dubbo協議為例
        //dubbo://ip:port/介面名?其他資訊
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置為none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠端服務)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local則暴露為遠端服務.(配置為local,則表示只暴露遠端服務)
            if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {//遍歷註冊中心

                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        //ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker例項,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        //Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是開啟socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo自己實現。
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    }

下一篇集中處理如下程式碼流程,這一篇只是初步瞭解了dubbo 服務暴露預處理的一些邏輯,
至於為什麼dubbo會選擇生成 registry:// 和 dubbo:// 如下兩種連結。以後篇幅討論.
後續將繼續跟蹤以下程式碼片段的實現。

 //ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker例項,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        //Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是開啟socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo自己實現。
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);