前言

源码入手

平时我要了解一个框架,基本会去从他的Listener入手,如果web.xml中没有配置listener可能还会有 filter,这是spring给我们的启示,可是当要去了解dubbo的时候,发现dubbo并没有自己的listener监听器。已知dubbo是一款和spring结合较好的rpc框架,那么其不使用web容器相关的方式,必然遵循spring的方式。依据平时开发经验,我们知道要想在Spring初始化之后,做一些自己的逻辑,有一种方法即实现org.springframework.beans.factory.InitializingBean接口,那么我们搜一下,dubbo有没有这么干。

dubbo exposed属性解析_spring

前文 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×tamp=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®istry=zookeeper×tamp=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);