本文紧接上文,doExportUrls()方法位于ServiceConfig类中,代码入口如下:

 private void doExportUrls() {List<URL> registryURLs = loadRegistries(true);    // 获取注册中心的配置for (ProtocolConfig protocolConfig : protocols) {  //获取配置的服务暴露协义doExportUrlsFor1Protocol(protocolConfig, registryURLs);  //真正的暴露,通过协义和注册中心去完成暴露}}

这样在一个比较宏观的粒度已经看明白了服务暴露,如果想深入请进入下一级分析。


下面以默认的dubbo协议,注册中心为zookeeper来分析 doExportUrlsFor1Protocol(protocolConfig, registryURLs) 方法,方法比较长大约200行,我们分成几段来看

第一段:

     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;if (NetUtils.isInvalidLocalHost(host)) {anyhost = true;try {host = InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {logger.warn(e.getMessage(), e);}if (NetUtils.isInvalidLocalHost(host)) {if (registryURLs != null && registryURLs.size() > 0) {for (URL registryURL : registryURLs) {try {Socket socket = new Socket();try {SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());socket.connect(addr, 1000);host = socket.getLocalAddress().getHostAddress();break;} finally {try {socket.close();} catch (Throwable e) {}}} catch (Exception e) {logger.warn(e.getMessage(), e);}}}if (NetUtils.isInvalidLocalHost(host)) {host = NetUtils.getLocalHost();}}}

这段的主要作用就是获取host,四种方式的获取:

protocolConfig.getHost()   //从配置中获取
InetAddress.getLocalHost().getHostAddress() //返回本机的ip地址
registryURL.getHost()     //获取注册中心的主机名
NetUtils.getLocalHost()   //遍历本地网卡,返回第一个合理的IP

为了确保获得的主机有效,还有相应的验证:

  public static boolean isInvalidLocalHost(String host) {
    return host == null  || host.length() == 0
               || host.equalsIgnoreCase("localhost")
             || host.equals("0.0.0.0")
             || (LOCAL_IP_PATTERN.matcher(host).matches());  // LOCAL_IP_PATTERN对应的正则式 Pattern.compile("127(\\.\\d{1,3}){3}$") 
      }


第二段: 这段主要是获取各个协义需要暴露的端口, 按照这样的优先级去获取  :       Protocol的实现类的默认端口 ——> Protocol的配置端口 ——> 随机端口
     Integer port = protocolConfig.getPort();if (provider != null && (port == null || port == 0)) {port = provider.getPort();}final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();if (port == null || port == 0) {port = defaultPort;}if (port == null || port <= 0) {port = getRandomPort(name);if (port == null || port < 0) {port = NetUtils.getAvailablePort(defaultPort);putRandomPort(name, port);}logger.warn("Use random available port(" + port + ") for protocol " + name);}


第三段 

   代码量很多,收集各类参数,放入map中,在为服务暴露做参数收集准备工作

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, application);appendParameters(map, module);appendParameters(map, provider, Constants.DEFAULT_KEY);appendParameters(map, protocolConfig);appendParameters(map, this);if (methods != null && methods.size() > 0) {  //服务接口的方法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");}}List<ArgumentConfig> arguments = method.getArguments();         //处理方法的参数,并看是否有回调参数if (arguments != null && arguments.size() > 0) {  for (ArgumentConfig argument : arguments) {//类型自动转换.if(argument.getType() != null && argument.getType().length() >0){Method[] methods = interfaceClass.getMethods();//遍历所有方法if(methods != null && methods.length > 0){for (int i = 0; i < methods.length; i++) {String methodName = methods[i].getName();//匹配方法名称,获取方法签名.if(methodName.equals(method.getName())){Class<?>[] argtypes = methods[i].getParameterTypes();//一个方法中单个callbackif (argument.getIndex() != -1 ){if (argtypes[argument.getIndex()].getName().equals(argument.getType())){appendParameters(map, argument, method.getName() + "." + argument.getIndex());}else {throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());}} else {//一个方法中多个callbackfor (int j = 0 ;j<argtypes.length ;j++) {Class<?> argclazz = argtypes[j];if (argclazz.getName().equals(argument.getType())){appendParameters(map, argument, method.getName() + "." + j);if (argument.getIndex() != -1 && argument.getIndex() != j){throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());}}}}}}}}else if(argument.getIndex() != -1){appendParameters(map, argument, method.getName() + "." + argument.getIndex());}else {throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");}}}} // end of methods for
        }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(); //获取包装类,包装类实质上就是通过javassist生成的类,当我们通过包装类对角调dubbo接口时实际调的还是接口对象的原有方法。此处包装起到的作用无非是可以以统一的代码的去调用用户提供的不同的dubbo接口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)), ","));}}if (! ConfigUtils.isEmpty(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");}

第四段:

这段主要是拼接URL,Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。

URL示例:dubbo://192.168.10.247:10080/org.huxin.dubbo.test.user.service.UserInterface?anyhost=true&application=dubbo-provider&default.retries=0&default.timeout=5000&dubbo=2.8.4&generic=false&getUserById.0.callback=true&interface=org.huxin.dubbo.test.user.service.UserInterface&methods=getUserById,getUserList,updateUsers&organization=huxin&owner=programmer&pid=6020&retries=0&serialization=kryo&side=provider&timestamp=1512955717318

// 导出服务String contextPath = protocolConfig.getContextpath();  //获取协议的上下文路径if ((contextPath == null || contextPath.length() == 0) && provider != null) {contextPath = provider.getContextpath();}//拼接urlURL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);//这里的ConfiguratorFactory是个扩展,可以用来设计自己的URL组成规则if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) {url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url);}

第五段:

根据scope的配置决定是作本地暴露还是远程暴露,做服务暴露从结果上看就是产生了一个特定服务的 Exporter 类,并将其存储在对应的ServiceBean实例的 exporters属性中。//private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

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);} //只有远程暴露才需要用到注册中心urlif (registryURLs != null && registryURLs.size() > 0&& url.getParameter("register", true)) {for (URL registryURL : registryURLs) {url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));//获取监控中心的URL,此处先暂不关心生成的细节,待到服务治理时才详细分析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);}//这里对参数做个介绍:                          参数1:ref就是接口实现类                             参数2:interfaceClass:接口类                             参数3:在registryURL上添加参数,key为"export",value就是前面产生的服务协义的url                        //proxyFactory的来历                          ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension()                          这个类实例最终就是com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory类的实例,这是由于ProxyFactory类上                           的注解决定的Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));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服务接口会先作本地暴露,然后再作远程暴露,也即一个服务接口两次暴露。

下面来张图总结一下:服务提供者暴露一个服务的详细过程

由于篇幅太长,准备另开两篇对本地暴露和远程暴露进行详细分析。

转载于:https://www.cnblogs.com/hzhuxin/p/7993860.html

dubbo源码分析12——服务暴露3_doExportUrls()方法分析相关推荐

  1. dubbo(5) Dubbo源码解析之服务调用过程

    来源:https://juejin.im/post/5ca4a1286fb9a05e731fc042 Dubbo源码解析之服务调用过程 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与 ...

  2. dubbo源码学习(四):暴露服务的过程

    dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点, ...

  3. Dubbo原理何源码解析之服务暴露

    2019独角兽企业重金招聘Python工程师标准>>> 一.框架设计 在官方<Dubbo 用户指南>架构部分,给出了服务调用的整体架构和流程: 另外,在官方<Dub ...

  4. Dubbo源码解析之服务路由策略

    1. 简介 服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者.在详细分析服务路由的源码之前,先来介绍一下服务路由是什么.服务路由包含一条 ...

  5. 暴露的全局方法_Dubbo源码解析实战 - 服务暴露原理

    欢迎关注全是干货的技术公众号 dubbo面试中比较喜欢问的两个点:服务发布和服务引用. 人性的拷问 服务发布过程中做了哪些事 dubbo都有哪些协议,他们之间有什么特点,缺省值是什么 什么是本地暴露和 ...

  6. dubbo(4) Dubbo源码解析之服务引入过程

    来源:https://juejin.im/post/5ca37314e51d454cb97d9c40 1. 简介 在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直连的方式引用服 ...

  7. dubbo源码1-暴露服务

    dubbo版本2.8 dubbo配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmln ...

  8. java warmup,20. dubbo源码-预热warmup过程

    阿飞Javaer,转载请注明原创出处,谢谢! 前言 今天群里小伙伴黄晓峰VIVO咨询一个问题:"dubbo接口怎么做预热呢,每次上线,都会有一小部分超时?",熟悉JVM都知道,JV ...

  9. dubbo源码解析之框架粗谈

    dubbo框架设计 一.dubbo框架整体设计 二.各层说明 三.dubbo工程模块分包 四.依赖关系 五.调用链 文章系列 [一.dubbo源码解析之框架粗谈] [二.dubbo源码解析之dubbo ...

  10. Dubbo源码解析 —— Router

    作者:肥朝 原文地址:http://www.jianshu.com/p/278e782eef85 友情提示:欢迎关注公众号[芋道源码].????关注后,拉你进[源码圈]微信群和[肥朝]搞基嗨皮. 友情 ...

最新文章

  1. Python使用sklearn构建广义线性模型:gamma回归(Gamma regression)实战
  2. 插件化知识储备-Binder和AIDL原理
  3. FFmpegMediaMetadataRetriever-native.aar
  4. autofac文档:服务类型,名称和键
  5. linux 安装tomcat遇到的问题
  6. Vue学习笔记(一)—— 什么时候需要import Vue from 'vue'
  7. 在spring中该如何使用DTO,以及DTO和Entity的关系
  8. python rtf转txt_将DOC、RTF格式文件批量转为TXT格式文件
  9. android studio生产签名文件,Android Studio生成keystore签名文件步骤讲解
  10. 【java奇思妙想】一个能够改变字体颜色、字号大小、字体样式的html网页
  11. Python学习之路-22 (面向对象特殊成员)
  12. PID算法基本原理及整定实现方法
  13. 最难学的10大编程语言排行榜,Java只排第三,第一名出乎意料
  14. 白话大数据 | 元宇宙来了,但是你了解元数据吗?
  15. 财务会计 -- 什么是借贷
  16. R语言绘图-抖动点图(jitter)绘制指南
  17. 计算机相关专业提升学历的解决方案(博士研究生)
  18. luooj1559最强阵容加强版
  19. 魔兽世界网站服务器搭建,网游服务器搭建方案图解(以魔兽世界为例)(示例代码)...
  20. 【Android开发VR实战】二.播放360°全景视频

热门文章

  1. java设置user.dir_使用java系统属性user.dir读取配置文件
  2. Linux获取外部程序指针,Linux内核获取当前进程指针
  3. 中职计算机专业学Excel,2013年中职计算机应用专业中文Excel电子表格处理实际操作练习.doc...
  4. phpstom怎样导出数据库_详解mysql数据库sql优化技巧总结
  5. 计算机网络工程税率多少,弱电工程增值税6%、9%、13%税率怎样选择?
  6. 开源中文bi_odoo:免费开源ERP入门与实践
  7. 双系统ubuntu无法进入_win10下安装ubuntu双系统
  8. cmake 交叉编译_如何使用CMake编译RTT微内核
  9. activiti高亮显示图片_Excel全自动甘特图,工作进度显示,极简设计快手套用
  10. 设置现金流量科目必须录入