dubbo源码解析(四) api配置之服务消费者

目录

前言:

1、dubbo服务提供者api

2、获取服务对象

3、获取代理对象


前言:

           在前面有关dubbo源码解析的博文中我们分析了api编码的形式配置服务消费者进行了服务的发布,下面我们继续使用api的形式进行暴露服务的消费。

1、dubbo服务提供者api

    /**
     * dubbo 服务消费者api形式进行服务消费
     */
    @Test
    public void consumerDubboService(){
        //声明应用 dubbo生态质检服务调用是基于应用的
        ApplicationConfig application = new ApplicationConfig("dubbo-refrence");
        //涉及注册中心
        RegistryConfig registryCenter = new RegistryConfig();
        registryCenter.setAddress("zookeeper://182.92.189.235:2181");

        //消费者消费
        //设置消费者全局配置
        ConsumerConfig consumerConfig = new ConsumerConfig();
        //设置默认的超时时间
        consumerConfig.setTimeout(1000*5);
        ReferenceConfig<UserService> userConfigReference = new ReferenceConfig<>();
        userConfigReference.setApplication(application);
//        List<RegistryConfig> registryConfigs = new ArrayList<>();
//        registryConfigs.add(registryCenter);
        userConfigReference.setRegistry(registryCenter);
        userConfigReference.setInterface(UserService.class);
        //设置methodConfig 方法级别的dubbo参数包配置 比如方法名必填、重试次数、超时时间、负载均衡策略
        MethodConfig methodConfig = new MethodConfig();
        //方法名必填
        methodConfig.setName("queryUserInfo");
        //超时时间
        methodConfig.setTimeout(1000 * 5);
        //重试次数
        methodConfig.setRetries(3);
        //获取服务(并非真实的对象而是代理对象)
        UserService userService = userConfigReference.get();
        //调用对象方法
        String info = userService.queryUserInfo();
        System.out.println(info);
    }

上述代码核心是构造了一个ReferenceConfig对象 该对应设置了应用信息配置(applicationConfig)、注册中心(RegistryConfig)、全局消费者配置(ConsumerConfig)、方法级别配置(MethodConfig)最终调用ReferenceConfig的get方法获取duubo服务对象进行服务消费。我们核心需要关注其get方法

2、获取服务对象

   ReferenceConfig的get方法最终会调用其对应的init()方法进行dubbo服务代理对象的创建,最终业务代码调用代理对象的方法进行服务消费下面来关注一下init()方法

private void init() {
    //如果该实例已经初始化 则返回
    if (initialized) {
        return;
    }
    //标记该实例初始化(先占坑) 防止重复实例化
    initialized = true;
    //消费接口非空校验
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
    }
    //获取全局服务消费者配置信息(ConsumerConfig) 逻辑:获取该对象配置的ConsumerConfig对象 并将系统属性中
    //涉及到服务消费者dubbo.consumer.xxx 相关的配置属性通过setXxx方法填充到ConsumerConfig对象中
    checkDefault();
    //服务消费者dubbo.reference.xxx 相关的配置属性通过setXxx方法填充到ReferenceConfig对象中
    appendProperties(this);
    //设置是否泛化接口标识
    if (getGeneric() == null && getConsumer() != null) {
        setGeneric(getConsumer().getGeneric());
    }
    //如果是泛化接口 class为GenericService
    if (ProtocolUtils.isGeneric(getGeneric())) {
        interfaceClass = GenericService.class;
    } else {
        try {
            //普通接口 反射获取class
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        //检查接口对应的class和method级别的参数配置
        //所谓方法级别的配置可以针对接口中的的某个方法进行 超时、重试次数、负载均衡策略等的设置
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    //获取直连dubbo对应的url
    //1、项目运行的时候jdk 命令启动 带参数java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
    //2、直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径  java -Ddubbo.resolve.file=xxx.properties 在xxx.properties中(key为服务名,value为服务提供者url)
    //3、xml配置直连 <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService"url="dubbo://localhost:20890" />
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    if (resolve == null || resolve.length() == 0) {
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
            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 = resolve;
        if (logger.isWarnEnabled()) {
            if (resolveFile != null && resolveFile.length() > 0) {
                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.");
            }
        }
    }
    //根据优先级从ConsumerConfig、ModuleConfig、ApplicationConfig获取相应的配置信息
    //比如注册中心、监控信息等
    if (consumer != null) {
        if (application == null) {
            application = consumer.getApplication();
        }
        if (module == null) {
            module = consumer.getModule();
        }
        if (registries == null) {
            registries = consumer.getRegistries();
        }
        if (monitor == null) {
            monitor = consumer.getMonitor();
        }
    }
    if (module != null) {
        if (registries == null) {
            registries = module.getRegistries();
        }
        if (monitor == null) {
            monitor = module.getMonitor();
        }
    }
    if (application != null) {
        if (registries == null) {
            registries = application.getRegistries();
        }
        if (monitor == null) {
            monitor = application.getMonitor();
        }
    }
    //获取application(应用)相关的信息填充application对象中
    //还会添加两个 有关dubbo(优雅)停机操作属性
    checkApplication();
    //添加mock、local、stub相关配置class
    checkStubAndMock(interfaceClass);
    //获取side、version、时间戳、pid、revision、methods、
    //application应用信息、module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
    // (用与生成服务消费相关的Dubbo URL)
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_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()));
    }
    if (!isGeneric()) {
        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 {
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    map.put(Constants.INTERFACE_KEY, interfaceName);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    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");
                }
            }
            appendAttributes(attributes, method, prefix + "." + method.getName());
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }
    //DUBBO_IP_TO_REGISTRY 属性可以显示设置服务消费者需要注册到注册中心的地址
    // (docker服务部署服务消费者可能会出现注册不是真实消费者的注册地址)
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
        //没有配置显示注册地址则获取宿主机真实地址
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    //添加MethodConfig相关的属性信息到系统上下文中
    StaticContext.getSystemContext().putAll(attributes);
    //创建代理对象
    ref = createProxy(map);
    //将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

上述代码比较长,但是逻辑和duubo服务发布者相似比较简单

1、该实例重复和接口为空校验
2、从系统变量获取属性设置到consumerConfig全局配置和ReferenceConfig中
3、获取接口class属性(1、普通接口:需要检查接口对应的class和method级别的参数配置 2泛化接口:GenericService)
4、获取直连dubbo 对应的服务接口
   获取方式又分成   
1、项目运行的时候jdk 命令启动 带参数
    java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
2、服务多的时候  直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径 
   java -Ddubbo.resolve.file=xxx.properties 
   在xxx.properties中(key为服务名,value为服务提供者url)
3、xml配置直连 <dubbo:reference id="xxxService" 
   interface="com.alibaba.xxx.XxxService"url="dubbo://localhost:20890" />
   api编码:userConfigReference.setUrl("dubbo://xxx.xxx.xx:22890")
   注解:  @Reference(url = "dubbo;//xxxxxx.xx:22200")
5、以consumerConfig -> moduleConfig -> applicationConfig的优先级顺序获取注册中心、监控等信息
6、获取side、version、时间戳、pid、revision、methods、application应用信息、
    module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
7、applicationConfig 配置信息填充
8、local、stub、mock相关class配置
9、获取宿主地址信息(可以显示配置ip)
10、添加MethodConfig相关的属性信息到系统上下文中
11、创建代理对象
12、将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中

 上述代码中我们需要重点关注的是createProxy()方法

3、获取代理对象

    private T createProxy(Map<String, String> map) {
        //判断是不是dubbo的本地调用 如果是本地调用则不发起远程服务,只进行本地关联 但执行 Dubbo 的 Filter 链
        //从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务
        //<dubbo:protocol name="injvm" />
        // <dubbo:provider protocol="injvm" />
        //<dubbo:service protocol="injvm" /> 
        //<dubbo:consumer injvm="true" .../>
        //<dubbo:provider injvm="true" .../>
        //<dubbo:reference injvm="true" .../>
        //<dubbo:service injvm="true" .../>
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        final boolean isJvmRefer;
        if (isInjvm() == null) {
            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();
        }
        //如果是本地调用获取本地调用的invoker  JVM内部调用
        if (isJvmRefer) {
            URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
            invoker = refprotocol.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {
            //直连调用逻辑
            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);
                        }
                        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                            urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            } else {
                //走注册中心
                //获取多个注册中心以及注册中心对应的监控中心 遍历将注册中心和监控中心添加到Dubbo URL 集合中 后面创建Invoker对象用
                //获取多个注册中心
                List<URL> us = loadRegistries(false);
                if (us != null && !us.isEmpty()) {
                    for (URL u : us) {
                        //获取注册中心对应的监控中心
                        URL monitorUrl = loadMonitor(u);
                        if (monitorUrl != null) {
                            map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                        }
                        //添加到DubboURL集合中 该Dubbo URL 是注册中心转换的url
                        urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    }
                }
                if (urls == null || 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.");
                }
            }
            //注册中心单个则使用该注册中心创建对应的Invoker
            if (urls.size() == 1) {
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
                //多个注册中心遍历处理 每一个注册中心创建一个Invoker添加到invokers集合中
                //最终选则最新的注册地址
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(refprotocol.refer(interfaceClass, url));
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // use AvailableCluster only when register's cluster is available
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                } else { // not a registry url
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
            }
        }
       //省略日志打印
        //使用ProxyFacrory创建dubbo服务对应的代理对象
        return (T) proxyFactory.getProxy(invoker);
    }

创建代理对象的逻辑分成三个模块

  1、本地调用Injvm

  2、直连URL调用

  3、注册中心注册

  这三种方式最终都会生成对应的com.alibaba.dubbo.rpc.Invoker 通过com.alibaba.dubbo.rpc.ProxyFactory创建代理对象。


版权声明:本文为liushangzaibeijing原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。