Dubbo的服務導出(二)


在分析完上一節 Dubbo配置文件解析 之後,接下來分析一下 dubbo 的服務導出(註冊)

服務配置類圖

在這裏插入圖片描述

Dubbo服務導出

1、dubbo服務導出入口

服務導出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一個事件響應方法,該方法會在收到 Spring 上下文刷新事件後執行服務導出操作。方法代碼如下:

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }
    private void doExportUrls() {
    	// 拼裝url
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            // 根據協議進行導出服務
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

2、加載註冊中心鏈接

    protected List<URL> loadRegistries(boolean provider) {
        // check && override if necessary
        List<URL> registryList = new ArrayList<URL>();
        if (CollectionUtils.isNotEmpty(registries)) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isEmpty(address)) {
                    address = ANYHOST_VALUE;
                }
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    appendParameters(map, application);
                    appendParameters(map, config);
                    map.put(PATH_KEY, RegistryService.class.getName());
                    appendRuntimeParameters(map);
                    if (!map.containsKey(PROTOCOL_KEY)) {
                        map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                    }
                    List<URL> urls = UrlUtils.parseURLs(address, map);

                    for (URL url : urls) {
                        url = URLBuilder.from(url)
                                .addParameter(REGISTRY_KEY, url.getProtocol())
                                .setProtocol(REGISTRY_PROTOCOL)
                                .build();
                        if ((provider && url.getParameter(REGISTER_KEY, true))
                                || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

循環遍歷 registries,獲取url,並且拼裝參數,獲取到的 registryURLs 如下
在這裏插入圖片描述
string =
“registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=tea-life-provider&check=true&dubbo=2.0.2&pid=7057&registry=zookeeper&release=2.7.3&timestamp=1572159342766&version=1.0”

3、導出服務

3.1、組裝Url
       String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }

        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, PROVIDER_SIDE);

        appendRuntimeParameters(map);
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);

解析 metricsConfig、applicationConfig、moduleConfig、providerConfig、protocolConfig、serviceConfig,將解析到的數據放入map

3.2、解析接口中的方法,隨機生成 token
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }

解析 接口中的方法,將其放入map

        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }

隨機生成token,將其放入map

3.3、服務導出
        // export service
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            // 導出本地服務
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            // 導出遠程服務
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {

                }
3.3.1 創建 Invoker

在這裏插入圖片描述
Dubbo 默認的 ProxyFactory 實現類是 JavassistProxyFactory

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 創建包裝類,解析接口中的信息
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        // doInvoke的時候,其實就是調用接口中方法的過程
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
3.3.2 Wapper類

個人覺得這個類還是很重要的,Wrapper.getWrapper(Clazz.class)的時候會創建Wapper,將接口的信息解析,存放在
Map<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap();
WRAPPER_MAP 對應的是 Class 與 Wraper的關係,Wraper中包含接口的方法,屬性等

public abstract class Wrapper {

    public static Wrapper getWrapper(Class<?> c) {
        while(ClassGenerator.isDynamicClass(c)) {
            c = c.getSuperclass();
        }

        if (c == Object.class) {
            return OBJECT_WRAPPER;
        } else {
            Wrapper ret = (Wrapper)WRAPPER_MAP.get(c);
            if (ret == null) {
                ret = makeWrapper(c);
                WRAPPER_MAP.put(c, ret);
            }

            return ret;
        }
    }
  
	// Wrapper 中包含着方法,屬性,還可以invokeMethod()
    private static final Wrapper OBJECT_WRAPPER = new Wrapper() {
        public String[] getMethodNames() {
            return Wrapper.OBJECT_METHODS;
        }

        public String[] getDeclaredMethodNames() {
            return Wrapper.OBJECT_METHODS;
        }

        public String[] getPropertyNames() {
            return Wrapper.EMPTY_STRING_ARRAY;
        }

        public Class<?> getPropertyType(String pn) {
            return null;
        }

        public Object getPropertyValue(Object instance, String pn) throws NoSuchPropertyException {
            throw new NoSuchPropertyException("Property [" + pn + "] not found.");
        }

        public void setPropertyValue(Object instance, String pn, Object pv) throws NoSuchPropertyException {
            throw new NoSuchPropertyException("Property [" + pn + "] not found.");
        }

        public boolean hasProperty(String name) {
            return false;
        }

        public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException {
            if ("getClass".equals(mn)) {
                return instance.getClass();
            } else if ("hashCode".equals(mn)) {
                return instance.hashCode();
            } else if ("toString".equals(mn)) {
                return instance.toString();
            } else if ("equals".equals(mn)) {
                if (args.length == 1) {
                    return instance.equals(args[0]);
                } else {
                    throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error.");
                }
            } else {
                throw new NoSuchMethodException("Method [" + mn + "] not found.");
            }
        }
    };

其實最後還是調用 Wrapper.invokeMethod()執行

3.3.3 遠程服務導出(包含服務註冊的過程)
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
						// 這裏進行的是遠程服務的導出,註冊
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }

export方法其實幹了兩件事:
1、將上一步拿到的Invoker export出去,然後創建 DubboExporter實例並存儲在 exporterMap 裏,Exporter並沒有什麼玄機,就是一個存儲了Invoker實例及其他各種信息的容器,用於之後獲取Invoker用。

2、打開服務端口,並註冊ExchangeHandler,用於後續響應客戶端網絡請求,響應邏輯這裏先不展開。

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