Dubbo的服务导出(二)

文章目录

      • 服务配置类图
      • Dubbo服务导出
        • 1、dubbo服务导出入口
        • 2、加载注册中心链接
        • 3、导出服务
          • 3.1、组装Url
          • 3.2、解析接口中的方法,随机生成 token
          • 3.3、服务导出
            • 3.3.1 创建 Invoker
            • 3.3.2 Wapper类
            • 3.3.3 远程服务导出(包含服务注册的过程)

在分析完上一节 Dubbo配置文件解析 之后,接下来分析一下 dubbo 的服务导出(注册)

服务配置类图

Dubbo的服务导出(二)_第1张图片

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 如下
Dubbo的服务导出(二)_第2张图片
string =
“registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=tea-life-provider&check=true&dubbo=2.0.2&pid=7057®istry=zookeeper&release=2.7.3×tamp=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的服务导出(二)_第3张图片
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, 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,用于后续响应客户端网络请求,响应逻辑这里先不展开。

你可能感兴趣的:(Dubbo)