假设我们有以下消费者配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	 <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="consumer-of-helloworld-app"  />
 
    <!-- 使用redis注册中心暴露发现服务地址 -->
    <dubbo:registry address="redis://localhost:6379" />
 	<!-- Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭check,否则服务临时不可用时,会抛出异常,拿到null引用,如果check=false,总是会返回引用,当服务恢复时,能自动连上 -->
    <!-- 生成远程服务代理,可以和本地bean一样使用demoService,check="false"关闭某个服务的启动检查,在没有提供者是会进行报错 -->
    <!-- init="true"饥饿初始化 -->
    <dubbo:reference id="service" interface="com.dubbo.service.UserService" />
    
    <!-- 关闭全部服务的启动检查,没有提供者是报错 -->
    <!-- <dubbo:consumer check="false"></dubbo:consumer> -->
    <!-- 延迟初始化 -->
    <!--<bean id="userAction" class="com.dubbo.action.UserAction" lazy-init="true">
    	<property name="service" ref="service"></property>
    </bean>-->
</beans>

reference标签被解析成ReferenceBean
它实现了spring的FactoryBean,ApplicationContextAware,InitializingBean,DisposableBean接口

ApplicationContextAware与DisposableBean的实现与提供者启动是一样的逻辑,这里不过多的说明,我们现在主要关注InitializingBean与FactoryBean接口
这个FactoryBean在提供者端是没有实现的接口,那个为什么消费端要有呢?其实猜都猜得到这个,实现这个接口是为了生成当前配置接口的代理对象,实现调用远程接口犹如调用本地接口一样。

下面,我们来学习InitializingBean的实现

public void com.alibaba.dubbo.config.spring.ReferenceBean#afterPropertiesSet() throws Exception {
    //检查是否有缺省配置ConsumerConfig
    if (getConsumer() == null) {
        //没有就到spring容器中找
        Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);
        //找到没?
        if (consumerConfigMap != null && consumerConfigMap.size() > 0) {
            //找到了
            ConsumerConfig consumerConfig = null;
            for (ConsumerConfig config : consumerConfigMap.values()) {
                //是默认的配置吗?
                if (config.isDefault() == null || config.isDefault().booleanValue()) {
                    //存在多个默认配置,这哪能忍啊,当前ReferenceBean自己不引用一个默认的ConsumerConfig就算了,还认这么多人叫爸爸,肯定要报错
                    if (consumerConfig != null) {
                        throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);
                    }
                    consumerConfig = config;
                }
            }
            //记录下来
            if (consumerConfig != null) {
                setConsumer(consumerConfig);
            }
        }
    }
    //有应用信息配置吗?
    if (getApplication() == null
            && (getConsumer() == null || getConsumer().getApplication() == null)) {
            //ReferenceBean自己没有,引用的默认配置ConsumerConfig也没有,那么只好到容器中找找看了
        Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
        //容器中有吗?
        if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
            //有的
            ApplicationConfig applicationConfig = null;
            for (ApplicationConfig config : applicationConfigMap.values()) {
                //出现了多个默认配置吗?
                if (config.isDefault() == null || config.isDefault().booleanValue()) {
                    if (applicationConfig != null) {
                        //出现了
                        throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                    }
                    applicationConfig = config;
                }
            }
            //把当前applicationConfig记为自己的默认的应用信息配置
            if (applicationConfig != null) {
                setApplication(applicationConfig);
            }
        }
    }
    //存在模块配置吗?
    if (getModule() == null
            && (getConsumer() == null || getConsumer().getModule() == null)) {
        //没有,那从容器中找吧
        Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
        //找到了吗?
        if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
            ModuleConfig moduleConfig = null;
            //找到了
            for (ModuleConfig config : moduleConfigMap.values()) {
                //不能出现多个默认的,要不然就报错
                if (config.isDefault() == null || config.isDefault().booleanValue()) {
                    if (moduleConfig != null) {
                        throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
                    }
                    moduleConfig = config;
                }
            }
            if (moduleConfig != null) {
                setModule(moduleConfig);
            }
        }
    }
    //它们,有配置配置了注册中心吗?
    if ((getRegistries() == null || getRegistries().isEmpty())
            && (getConsumer() == null || getConsumer().getRegistries() == null || getConsumer().getRegistries().isEmpty())
            && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
        //额,一个都没有,那到容器中找吧
        Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
        //有没有找到注册配置
        if (registryConfigMap != null && registryConfigMap.size() > 0) {
            List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
            for (RegistryConfig config : registryConfigMap.values()) {
                //注册中可以有多个默认的
                if (config.isDefault() == null || config.isDefault().booleanValue()) {
                    registryConfigs.add(config);
                }
            }
            if (registryConfigs != null && !registryConfigs.isEmpty()) {
                super.setRegistries(registryConfigs);
            }
        }
    }
    //有注册中心吗?
    if (getMonitor() == null
            && (getConsumer() == null || getConsumer().getMonitor() == null)
            && (getApplication() == null || getApplication().getMonitor() == null)) {
            //那好吧,我去给你找个中心配置
        Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
        //找到吗?
        if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
            MonitorConfig monitorConfig = null;
            for (MonitorConfig config : monitorConfigMap.values()) {
                if (config.isDefault() == null || config.isDefault().booleanValue()) {
                    if (monitorConfig != null) {
                        //找到了,还不止一个
                        throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
                    }
                    monitorConfig = config;
                }
            }
            if (monitorConfig != null) {
                setMonitor(monitorConfig);
            }
        }
    }
    //初始化了吗?
    Boolean b = isInit();
    if (b == null && getConsumer() != null) {
        //如果当前引用配置没有进行初始化,那么看下ConsumerConfig有没有进行初始化
        b = getConsumer().isInit();
    }
    if (b != null && b.booleanValue()) {
        getObject();
    }
}

引用配置的初始化,初始化其实就是生成接口代理对象,这也是实现FactoryBean接口要干的事情,下面我继续看到getObject方法

public Object com.alibaba.dubbo.config.spring.ReferenceBean#getObject() throws Exception {
    return get();
}
                                        |
                                        V
public synchronized T com.alibaba.dubbo.config.ReferenceConfig#get() {
    //如果bean已经被销毁就不允许继续初始化了
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    if (ref == null) {
        init();
    }
    return ref;
}
                                        |
                                        V
private void com.alibaba.dubbo.config.ReferenceConfig#init() {
    if (initialized) {
        return;
    }
    initialized = true;
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
    }
    // get consumer's global configuration
    //检查是否有默认的配置,这个默认的配置就是ConsumerConfig,如果确实没有,那么也会创建一个consumerConfig,然后尝试从系统变量,系统属性的获取值
    checkDefault();
    //从系统属性,变量,获取通过系统属性指定的目标配置文件获取属性值,追加参数,值得注意的是,如果当前属性有值了,系统属性配置的值具有更高的优先级
    appendProperties(this);
    //是否使用泛化接口
    if (getGeneric() == null && getConsumer() != null) {
        setGeneric(getConsumer().getGeneric());
    }
    //检查是否需要使用泛化调用,一般用于消费端没有对应接口依赖包时使用
    //可以指定三种泛化时对参数进行的序列化方式:
    //Constants.GENERIC_SERIALIZATION_DEFAULT:这种方法在我们分析解析请求体数据的时候可以看到,dubbo使用PojoUtil助手类进行通用参数类型转换
    //Constants.GENERIC_SERIALIZATION_NATIVE_JAVA:这种方式就是使用的JDK的ObjectInputStream
    //Constants.GENERIC_SERIALIZATION_BEAN:这种使用的是dubbo的JavaBeanDescriptor方式,这个没有仔细研究
    if (ProtocolUtils.isGeneric(getGeneric())) {
        //接口类型修改为GenericService,但是interfaceName不会发生改动,它还要通过它找到远在服务端的接口
        //这个GenericService接口的方法只有一个,名字叫$invoke,有三个参数,第一个参数为目标接口的方法名,第二个参数
        //是目标方法的参数类型,第三个参数就是传给目标方法的参数值
        interfaceClass = GenericService.class;
    } else {
        try {
            //解析接口class对象
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        //检查配置的细粒度方法配置是否是当前接口的方法
        //配置methods是为了做到方法级别的控制,比如重试次数,超时时间都可以不同,就像有些方法处理逻辑比较复杂
        //这个方法可能就需要更多的时间去等待
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    //以接口名为key获取系统属性,属性值为消费者与提供者的点对点直连
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    if (resolve == null || resolve.length() == 0) {
        //没有,那么从系统属性的dubbo.resolve.file获取properties配置文件
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
            //还是没有,那么从用户目录下寻找dubbo-resolve.properties
            File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
                resolveFile = userResolveFile.getAbsolutePath();
            }
        }
        //读取配置文件
        if (resolveFile != null && resolveFile.length() > 0) {
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(new File(resolveFile));
                properties.load(fis);
            } catch (IOException e) {
                throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
            } finally {
                try {
                    if (null != fis) fis.close();
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
            resolve = properties.getProperty(interfaceName);
        }
    }
    if (resolve != null && resolve.length() > 0) {
        //点对点消费者与提供者互连的url
        url = resolve;
        if (logger.isWarnEnabled()) {
            if (resolveFile != null) {
                logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
            } else {
                logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
            }
        }
    }

    //。。。。。。省略部分配置是否为空的判断    
    //依然是检查应用信息是否配置,没有配置就创建一个空的,然后从系统属性,变量或者指定的配置文件中获取
    checkApplication();、
    //检查是否存在存根服务,所谓存根服务就是每次发起服务调用的时候就会调用这个存个服务,它实现了当前消费接口,一般用于
    //检查是否有缓存,如果没有再调用代理的接口实现
    //如果没有指定存根实现服务的类名,那么默认以接口名+"Local",这个类名,我们在配置消费者时配置local属性,这个local属于被废弃的属性
    //新的应该使用stub,对应的默认的类名就是接口名+"Stub"
    checkStub(interfaceClass);
    //检查mock服务,里面的逻辑我们在第二小节的时候就已经分析过了,此处不再赘述
    //mock的默认类名就是接口名+"Mock",在远程接口调用失败的时候,会调用这个服务。
    checkMock(interfaceClass);
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    //添额外参数,表示当前配置属于消费端
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
    //dubbo协议版本
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    //创建时间戳
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    //设置当前消费者运行的jvm进程号
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    //如果不是泛化的
    if (!isGeneric()) {
        //获取调整版本,首先从jar的META-INF中找,如果没有,再到当前接口所在的jar名字上找,如果还是没有就是用配置的默认版本
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }
        //获取这个接口的所有public方法名,这个Wrapper的具体操作逻辑我们在第二节的提供者配置分析过,通过javassist进行生成的一个可以获取属性,方法
        //设置属性,调用方法的类,具体的生成方式此处不再赘述
        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 {
            //使用逗号分割,记录当前接口存在的所有public方法
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    //interface -> 接口名
    map.put(Constants.INTERFACE_KEY, interfaceName);
    //添加应用信息
    appendParameters(map, application);
    //添加模块信息
    appendParameters(map, module);
    //添加默认的消费者配置,默认的配置的key都会有一个default前缀
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    //添加自身的配置
    appendParameters(map, this);
    //group名/接口名/我们配置的版本
    String prefix = StringUtils.getServiceKey(map);
    if (methods != null && !methods.isEmpty()) {
        for (MethodConfig method : methods) {
            //添加方法配置参数,以方法名作为前缀
            appendParameters(map, method, method.getName());
            String retryKey = method.getName() + ".retry";
            //将老的配置方式更正为新的配置方式
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            //添加属性,以group名/接口名/我们配置的版本.方法名最前缀
            appendAttributes(attributes, method, prefix + "." + method.getName());
            //检查和转换方法配置的onreturn,onthrow,oninvoke对应的方法是否存在,并把String类型的方法名修改为具体的Method对象
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }
    //获取用于注册的host
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
        //没有配置就获取本地ip地址
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        //无效的地址
        throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    }
    //添加注册ip地址
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    //attributes are stored by system context.
    //将属性值存储起来
    StaticContext.getSystemContext().putAll(attributes);
    //创建代理对象
    ref = createProxy(map);
    //将当前消费者进行包装,记录在com.alibaba.dubbo.config.model.ApplicationModel#consumedServices Map集合中
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

createProxy

private T com.alibaba.dubbo.config.ReferenceConfig#createProxy(Map<String, String> map) {
    //创建一个临时的Url
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    final boolean isJvmRefer;
    //是否直接本地连接
    if (isInjvm() == null) {
        //如果指定了消费者与提供者直连的url,那么取消本地应用
        if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
            isJvmRefer = false;
            //检测是否需要直接本地连接
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            // by default, reference local service if there is
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        isJvmRefer = isInjvm().booleanValue();
    }

    if (isJvmRefer) {
        //构建本地连接协议地址 injvm://127.0.0.1:0/接口名?map参数(变成key1=value1&key2=value2字符串)
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        //启动socket连接提供者,返回一个invoker
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        //用户指定的url地址,可能是消费者与提供者直连地址,也有可能是注册中心地址
        if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
            //分号分割
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    //设置路径为接口名
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        url = url.setPath(interfaceName);
                    }
                    //是否是registry协议
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        //refer -> map参数
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        //合并当前配置的map参数值到用户提供的url的参数中
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else { // assemble URL from register center's configuration
            //获取注册中心地址,并处理用户配置的注册中心地址,url的协议被修改为registy,真正的协议被设置为url的参数
            //加入注册中心为redis,那么这个url就会有一个register-》redis 参数,加载注册中心地址的逻辑在分析提供者的时候已经讲过
            //此处不再追溯了
            List<URL> us = loadRegistries(false);
            if (us != null && !us.isEmpty()) {
                for (URL u : us) {
                    //加载注册中心地址
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        //添加参数  monitor -》 monitorUrl.toFullString()
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    //添加refer-》map参数(变成那种key1=value1&key2=value2的形式)
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            //没有指定注册中心,抛错
            if (urls.isEmpty()) {
                throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
            }
        }
        //如果只有一个,直接连接
        if (urls.size() == 1) {
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            //有多个注册中心/直连提供者地址,创建多个连接
            for (URL url : urls) {
                invokers.add(refprotocol.refer(interfaceClass, url));
                //是否是注册url,判断其协议是否为registry
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    //记录最后一个注册中心地址
                    registryURL = url; // use last registry url
                }
            }
            //如果不为空,那么当前url就是注册url
            if (registryURL != null) { // registry url is available
                // use AvailableCluster only when register's cluster is available
                //添加标记,表示这个url是一个注册中心地址,并且在注册中心集群中是个可用的地址
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                //进行路由匹配,负载均衡获取其中一个invoker
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else { // not a registry url
                //不是注册中心地址,那么就是直连地址
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }
    //启动时检查提供者是否存在,true报错,false忽略
    Boolean c = check;
    if (c == null && consumer != null) {
        //检查默认配置是否配置了这个值
        c = consumer.isCheck();
    }
    //如果没有配置,那么默认为true
    if (c == null) {
        c = true; // default true
    }
    //检查提供者是否可用,不可用,抛出错误
    if (c && !invoker.isAvailable()) {
        // make it possible for consumer to retry later if provider is temporarily unavailable
        initialized = false;
        throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
    }
    if (logger.isInfoEnabled()) {
        logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
    }
    // create service proxy
    //创建代理,默认使用JavassistProxyFactory创建代理,它的代理逻辑我们在前面分析接口的调用的时候讲过了
    //此处不再赘述
    return (T) proxyFactory.getProxy(invoker);
}

上面有这么一段代码
invoker = refprotocol.refer(interfaceClass, url);
这个refprotocol是通过SPI加载的Protocol实现,它由javassit生成的一段代码,代码如下

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    //对于没有没有被@Adaptive注解修饰的方法直接抛出不支持异常
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    //。。。。。。省略获取默认端口方法
    
    //。。。。。。省略服务export方法

    //查找服务,arg0:接口class对象,arg1:注册中心地址或者直连服务提供者的地址
    public com.alibaba.dubbo.rpc.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        //对于注册中心,这里的协议是registry
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        //既然是注册中心url,那么这里获取的协议就是RegistryProtocol,如果是直连提供者的地址并且提供协议为dubbo,那么获取就是DubboProtocol
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

RegistryProtocol#refer

public <T> Invoker<T> com.alibaba.dubbo.registry.integration.RegistryProtocol#refer(Class<T> type, URL url) throws RpcException {
    //恢复原来配置的协议,假设我们原来配置的是一个redis://xxx地址,那么协议就会变回redis
    //redis://localhost:6379/com.alibaba.dubbo.registry.RegistryService?application=consumer-of-helloworld-app&dubbo=2.0.2&pid=28380&refer=application%3Dco
    //nsumer-of-helloworld-app%26dubbo%3D2.0.2%26interface%3Dcom.dubbo.service.UserService%26methods%3DsayHello%26pid%3D28380%26register.ip%3D192.168.56.1%26
    //side%3Dconsumer%26timestamp%3D1568454846115&timestamp=1568455009235
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    //从注册工厂中获取对应协议的注册器,假设我们使用的是redis,那么这里返回的就是RedisRegistry
    //RedisRegistry的构造器逻辑我们在第9小节服务的注册中分析过,此处不再额外分析
    Registry registry = registryFactory.getRegistry(url);
    //如果接口类型是注册服务,RedisRegistry就是注册服务的实现者
    if (RegistryService.class.equals(type)) {
        //直接使用把当前构建出来的RedisRegistry实例包装成invoker返回
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // group="a,b" or group="*"
    //将refer参数由key value字符串解析会map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    //获取接口服务分组
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        //存在分组
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
                //getMergeableCluster()获取的是一个MergeableCluster,如果参数中指定了merger参数
                //以点开头,比如 .merge,表示接口返回值存在一个merge方法,dubbo将所有符合条件的invoker返回值进行merge
                //如果不是以点开头的,那么判断返回值是否是集合类型,如果是的话,dubbo将创建自己实现了Merger的集合去merge
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    //普通cluster,虽然是通过javassist生成的实例,但是默认最终使用的就是FailoverCluster
    return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> com.alibaba.dubbo.registry.integration.RegistryProtocol#doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    //构建RegistryDirectory,这个从名字上叫做注册目录,它的接口有一个list方法,返回值是一个invoker列表
    //简单理解为是一个维护invoker列表的目录吧
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    //设置注册器
    directory.setRegistry(registry);
    //设置协议对象
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    //构建订阅url
    //consumer://host:port/接口名?参数对
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        //给url添加类目信息参数:category -》 consumers, check -》 false
        URL registeredConsumerUrl = getRegisteredConsumerUrl(subscribeUrl, url);
        //注册这个地址registeredConsumerUrl,注册逻辑和第九节我们分析提供者地址的注册是一样的,不过多说明
        //只不过它所注册的类目是consumers
        registry.register(registeredConsumerUrl);
        //记录这个被注册的消费者url到RegistryDirectory中
        directory.setRegisteredConsumerUrl(registeredConsumerUrl);
    }
    //订阅providers,configurators,routers,当这些redis通道发生变化时会收到提醒
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));
    //如果没有接口group,那么这个cluster最终调用的就是FailoverCluster),
    //它的join方法返回FailoverClusterInvoker(失败转移,当某个提供者调不通的时候会重新从列表中筛选一个)
    Invoker invoker = cluster.join(directory);
    //用一个com.alibaba.dubbo.registry.support.ProviderConsumerRegTable#consumerInvokers map集合记录
    //消费者信息
    //(*1*)
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}
//(*1*)
public static void registerConsumer(Invoker invoker, URL registryUrl, URL consumerUrl, RegistryDirectory registryDirectory) {
    //(*2*)
    //将消费信息进行打包
    ConsumerInvokerWrapper wrapperInvoker = new ConsumerInvokerWrapper(invoker, registryUrl, consumerUrl, registryDirectory);
    //group名/接口名:version
    String serviceUniqueName = consumerUrl.getServiceKey();
    //从缓存中获取
    Set<ConsumerInvokerWrapper> invokers = consumerInvokers.get(serviceUniqueName);
    if (invokers == null) {
        consumerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ConsumerInvokerWrapper>());
        invokers = consumerInvokers.get(serviceUniqueName);
    }
    invokers.add(wrapperInvoker);
}

//(*2*)
//就是单纯的用一个bean来保存数据而已
public ConsumerInvokerWrapper(Invoker<T> invoker, URL registryUrl, URL consumerUrl, RegistryDirectory registryDirectory) {
    this.invoker = invoker;
    this.originUrl = URL.valueOf(invoker.getUrl().toFullString());
    this.registryUrl = URL.valueOf(registryUrl.toFullString());
    this.consumerUrl = consumerUrl;
    this.registryDirectory = registryDirectory;
}

订阅redis通道

//url:consumerUrl(协议为consumer://本地机器ip/当前消费者接口名?当前消费者配置参数),listene:RegistryDirectory
public void com.alibaba.dubbo.registry.support.FailbackRegistry#subscribe(URL url, NotifyListener listener) {
    (*1*)
    //父类方法对url与listener做了空判断,然后将url与listerner以url -> List<listener>的形式注册到subscribed map集合中
    super.subscribe(url, listener);
    //移除以url为key的订阅失败的,取消订阅失败的,通知失败的监听器
    removeFailedSubscribed(url, listener);
    try {
        // Sending a subscription request to the server side
        //订阅
        doSubscribe(url, listener);
    } catch (Exception e) {
        
        。。。。。。省略部分异常处理
        // Record a failed registration request to a failed list, retry regularly
        //记录订阅失败的url,在创建Redis注册器时构建一个调度器,每个一段时间会进行重试
        addFailedSubscribed(url, listener);
    }
}

//(*1*)
public void subscribe(URL url, NotifyListener listener) {
    if (url == null) {
        throw new IllegalArgumentException("subscribe url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("subscribe listener == null");
    }
    if (logger.isInfoEnabled()) {
        logger.info("Subscribe: " + url);
    }
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    listeners.add(listener);
}

doSubscribe

//这个url是消费端url
public void doSubscribe(final URL url, final NotifyListener listener) {
    // /dubbo/com.dubbo.service.UserService
    String service = toServicePath(url);
    //获取通知器,Notifier是一个继承了Thread线程类
    Notifier notifier = notifiers.get(service);
    if (notifier == null) {
        //创建一个新的通知器,用于监听提供者的注册和注销,以便能动态更新提供者列表
        Notifier newNotifier = new Notifier(service);
        notifiers.putIfAbsent(service, newNotifier);
        notifier = notifiers.get(service);
        if (notifier == newNotifier) {
            //启动通知器
            notifier.start();
        }
    }
    boolean success = false;
    RpcException exception = null;
    //循环每台redis服务连接池
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
        JedisPool jedisPool = entry.getValue();
        try {
            //获取redis连接
            Jedis jedis = jedisPool.getResource();
            try {
                //如果key是*结尾的
                if (service.endsWith(Constants.ANY_VALUE)) {
                    admin = true;
                    //获取所有匹配的值
                    Set<String> keys = jedis.keys(service);
                    if (keys != null && !keys.isEmpty()) {
                        Map<String, Set<String>> serviceKeys = new HashMap<String, Set<String>>();
                        for (String key : keys) {
                            //获取接口名
                            String serviceKey = toServicePath(key);
                            //以接口名进行分组
                            Set<String> sk = serviceKeys.get(serviceKey);
                            if (sk == null) {
                                sk = new HashSet<String>();
                                serviceKeys.put(serviceKey, sk);
                            }
                            sk.add(key);
                        }
                        //循环以接口分组的值
                        for (Set<String> sk : serviceKeys.values()) {
                            doNotify(jedis, sk, url, Arrays.asList(listener));
                        }
                    }
                } else {
                    doNotify(jedis, jedis.keys(service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE), url, Arrays.asList(listener));
                }
                success = true;
                break; // Just read one server's data
            } finally {
                jedis.close();
            }
        } catch (Throwable t) { // Try the next server
            exception = new RpcException("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
        }
    }
    if (exception != null) {
        if (success) {
            logger.warn(exception.getMessage(), exception);
        } else {
            throw exception;
        }
    }
}

doNotify

private void com.alibaba.dubbo.registry.redis.RedisRegistry#doNotify(Jedis jedis, Collection<String> keys, URL url, Collection<NotifyListener> listeners) {
    if (keys == null || keys.isEmpty()
            || listeners == null || listeners.isEmpty()) {
        return;
    }
    long now = System.currentTimeMillis();
    List<URL> result = new ArrayList<URL>();
    //获取类目,providers,configurators,routers
    List<String> categories = Arrays.asList(url.getParameter(Constants.CATEGORY_KEY, new String[0]));
    //获取消费者接口名
    String consumerService = url.getServiceInterface();
    for (String key : keys) {
        if (!Constants.ANY_VALUE.equals(consumerService)) {
            //获取当前key的提供者接口名,不出意外的话,和我们消费者的接口名是一样的
            //因为我们调用jedis.keys的时候使用的通常就是/dubbo/接口名/*去匹配的
            String prvoiderService = toServiceName(key);
            if (!prvoiderService.equals(consumerService)) {
                continue;
            }
        }
        //获取类目,如果我们启动了服务提供者,那么redis中就会存在名为prvoiders的类目
        //并且在前面dubbo注册了消费者url,所有又会有consumers类目
        String category = toCategoryName(key);
        //如果当前key不是我们所需要订阅的类目,那么跳过
        if (!categories.contains(Constants.ANY_VALUE) && !categories.contains(category)) {
            continue;
        }
        List<URL> urls = new ArrayList<URL>();
        //获取这个key的所有值,如果是提供者,那么这个map就是提供者URL-》过期时间戳
        Map<String, String> values = jedis.hgetAll(key);
        if (values != null && values.size() > 0) {
            for (Map.Entry<String, String> entry : values.entrySet()) {
                //url
                URL u = URL.valueOf(entry.getKey());
                //非动态的,或者这个提供者地址没有过期,进行匹配
                //非动态服务提供者不会进行更新,使用都需要人工启动,提供者挂掉不会取消注册
                //动态服务提供者会动态刷新,当提供者挂掉后,会自动移除注册
                if (!u.getParameter(Constants.DYNAMIC_KEY, true)
                        || Long.parseLong(entry.getValue()) >= now) {
                    if (UrlUtils.isMatch(url, u)) {
                        urls.add(u);
                    }
                }
            }
        }
        //如果没有找到任何的提供者,路由或者配置器,添加一个协议为empty
        //地址为0.0.0.0,当前key的接口名,类别为当前类目名的空url
        if (urls.isEmpty()) {
            urls.add(url.setProtocol(Constants.EMPTY_PROTOCOL)
                    .setAddress(Constants.ANYHOST_VALUE)
                    .setPath(toServiceName(key))
                    .addParameter(Constants.CATEGORY_KEY, category));
        }
        result.addAll(urls);
        if (logger.isInfoEnabled()) {
            logger.info("redis notify: " + key + " = " + urls);
        }
    }
    //为空,直接返回
    if (result == null || result.isEmpty()) {
        return;
    }
    //通知
    for (NotifyListener listener : listeners) {
        notify(url, listener, result);
    }
}

上面一段代码主要是获取类别为providers,configurators,routers下有效的url

protected void com.alibaba.dubbo.registry.support.AbstractRegistry#notify(URL url, NotifyListener listener, List<URL> urls) {
    //当前消费者url
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    //通知观察者
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    if ((urls == null || urls.isEmpty())
            && !Constants.ANY_VALUE.equals(url.getServiceInterface())) {
        logger.warn("Ignore empty notify urls for subscribe url " + url);
        return;
    }
    if (logger.isInfoEnabled()) {
        logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
    }
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    for (URL u : urls) {
        //是否是与消费者url相匹配的url,匹配条件大致是接口相同,组,版本相同,但是类目不能相同等等
        if (UrlUtils.isMatch(url, u)) {
            //获取提供者或是路由或是匹配器类目
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            //进行类目分组
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    //没有直接返回
    if (result.size() == 0) {
        return;
    }
    //消费者url -》 (类目 -》 redis类目下对应的复合条件的url)
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        //获取类目
        String category = entry.getKey();
        //获取该类目下未过期且消费者url匹配的url
        List<URL> categoryList = entry.getValue();
        //记录到缓存中
        categoryNotified.put(category, categoryList);
        //以key为消费者url,value为url空格分隔的形式存到 用户目录\.dubbo\dubbo-registry-consumer-of-helloworld-app-localhost:6379.cache
        //properties文件中
        saveProperties(url);
        //调用监听器
        listener.notify(categoryList);
    }
}

上面一段代码主要在给提供者url/配置器url/路由url进行分类并把这些url进行写入硬盘备份,最后调用了com.alibaba.dubbo.registry.integration.RegistryDirectory#notify方法,这个方法是用来做什么的呢?这个方法的逻辑在下一节分析。