blob: c1053180d5970cda2271b27cf5ffeb297d3c56ad [file] [log] [blame]
<!doctype html><html lang=zh-cn class=no-js><head><meta name=ROBOTS content="INDEX, FOLLOW"><link rel=canonical href=https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/><script>var _hmt=_hmt||[];(function(){var e,t=document.createElement("script");t.src="https://hm.baidu.com/hm.js?3b78f49ba47181e4d998a66b689446e9",e=document.getElementsByTagName("script")[0],e.parentNode.insertBefore(t,e)})()</script><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta http-equiv=Content-Security-Policy content="frame-src *"><meta name=generator content="Hugo 0.122.0"><link rel="shortcut icon" type=image/png href=/imgs/favicon.png><link rel=apple-touch-icon href=/favicons/apple-touch-icon-180x180.png sizes=180x180><link rel=manifest href=/manifest.webmanifest><title>服务导出 | Apache Dubbo</title><meta property="og:title" content="服务导出">
<meta property="og:description" content="本文介绍了 Dubbo 服务导出的过程和实现细节"><meta property="og:type" content="article"><meta property="og:url" content="https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/"><meta property="article:section" content="docsv2.7"><meta property="article:modified_time" content="2023-03-01T09:48:32+08:00"><meta itemprop=name content="服务导出"><meta itemprop=description content="本文介绍了 Dubbo 服务导出的过程和实现细节"><meta itemprop=dateModified content="2023-03-01T09:48:32+08:00"><meta itemprop=wordCount content="4706"><meta itemprop=keywords content><meta name=twitter:card content="summary"><meta name=twitter:title content="服务导出"><meta name=twitter:description content="本文介绍了 Dubbo 服务导出的过程和实现细节"><script async src="https://www.googletagmanager.com/gtag/js?id=G-NM6FFMT51J"></script><script>var doNotTrack=!1;if(!doNotTrack){window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("js",new Date),gtag("config","G-NM6FFMT51J",{anonymize_ip:!1})}</script><link rel=preload href=/scss/main.min.f77e221bcdbe0cadb996060fe82063c747b60c229a1f8bbf0ee529adbadd84fa.css as=style><link href=/scss/main.min.f77e221bcdbe0cadb996060fe82063c747b60c229a1f8bbf0ee529adbadd84fa.css rel=stylesheet integrity><script src=/js/jquery-3.5.1.min.js integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin=anonymous></script><meta name=theme-color content="#326ce5"><link rel=stylesheet href=/css/feature-states.css><meta name=description content="本文介绍了 Dubbo 服务导出的过程和实现细节"><meta property="og:description" content="本文介绍了 Dubbo 服务导出的过程和实现细节"><meta name=twitter:description content="本文介绍了 Dubbo 服务导出的过程和实现细节"><meta property="og:url" content="https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/"><meta property="og:title" content="服务导出"><meta name=twitter:title content="服务导出"><meta name=twitter:image:alt content="Apache Dubbo"><meta property="og:type" content="article"><meta name=viewport content="width=device-width"><script async defer src=/js/github-buttons.js></script><link href=/css/community.css rel=stylesheet><link href=/css/contactus.css rel=stylesheet><link href=/css/language.css rel=stylesheet><script src=/js/script.js></script></head><body class="td-page td-documentation"><header><nav class="js-navbar-scroll navbar navbar-expand navbar-dark flex-column flex-md-row td-navbar" data-auto-burger=primary><a class=navbar-brand href=/zh-cn/><span class=navbar-logo></span><span class="text-uppercase font-weight-bold">Apache Dubbo</span></a><div class="td-navbar-nav-scroll ml-md-auto" id=main_navbar><ul class="navbar-nav mt-2 mt-lg-0"><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=/zh-cn/overview/home/><span>文档</span></a></li><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=/zh-cn/blog/><span>博客</span></a></li><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=/zh-cn/download/><span>版本发布</span></a></li><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=/zh-cn/contact/><span>联系社区</span></a></li><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=https://start.dubbo.apache.org/bootstrap.html target=_blank><span>Initializer</span><i class='fas fa-external-link-alt'></i></a></li><li class="nav-item mr-4 mb-2 mb-lg-0"><a class=nav-link href=/zh-cn/github/><span>Github</span><i class='fa-brands fa-github'></i></a></li><li class="nav-item dropdown d-lg-block"><a class="nav-link dropdown-toggle" href=# id=navbarDropdownMenuLink role=button data-toggle=dropdown aria-haspopup=true aria-expanded=false>中文</a><div class="dropdown-menu dropdown-menu-right" aria-labelledby=navbarDropdownMenuLink><a class=dropdown-item href=/en/>English</a></div></li><li class="nav-item dropdown d-lg-block"><div class="nav-item d-none d-lg-block"></div></li></ul></div></nav><section class="header-hero text-white pb-0 light-text"></section></header><div class="container-fluid td-outer"><div class=td-main><div class="row flex-md-nowrap"><div class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none"><script>$(function(){$("#td-section-nav a").removeClass("active"),$("#td-section-nav #m-zh-cndocsv27devsourceexport-service").addClass("active"),$("#td-section-nav #m-zh-cndocsv27devsourceexport-service-li span").addClass("td-sidebar-nav-active-item"),$("#td-section-nav #m-zh-cndocsv27devsourceexport-service").parents("li").addClass("active-path"),$("#td-section-nav li.active-path").addClass("show"),$("#td-section-nav li.active-path").children("input").prop("checked",!0),$("#td-section-nav #m-zh-cndocsv27devsourceexport-service-li").siblings("li").addClass("show"),$("#td-section-nav #m-zh-cndocsv27devsourceexport-service-li").children("ul").children("li").addClass("show"),$("#td-sidebar-menu").toggleClass("d-none")})</script><div id=td-sidebar-menu class="td-sidebar__inner d-none"><div id=content-mobile><form class="td-sidebar__search d-flex align-items-center"><button class="btn btn-link td-sidebar__toggle d-md-none p-0 ml-3 fas fa-bars" type=button data-toggle=collapse data-target=#td-section-nav aria-controls=td-docs-nav aria-expanded=false aria-label="Toggle section navigation"></button></form></div><div id=content-desktop></div><nav class="collapse td-sidebar-nav foldable-nav" id=td-section-nav><ul class="td-sidebar-nav__section pr-md-3 ul-0"><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27-li><ul class=ul-1><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27user-li><input type=checkbox id=m-zh-cndocsv27user-check>
<label for=m-zh-cndocsv27user-check><a href=/zh-cn/docsv2.7/user/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27user><span>用户文档</span></a></label><ul class="ul-2 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userpreface-li><input type=checkbox id=m-zh-cndocsv27userpreface-check>
<label for=m-zh-cndocsv27userpreface-check><a href=/zh-cn/docsv2.7/user/preface/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userpreface><span>入门介绍</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userprefacebackground-li><input type=checkbox id=m-zh-cndocsv27userprefacebackground-check>
<label for=m-zh-cndocsv27userprefacebackground-check><a href=/zh-cn/docsv2.7/user/preface/background/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userprefacebackground><span>背景</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userprefacerequirements-li><input type=checkbox id=m-zh-cndocsv27userprefacerequirements-check>
<label for=m-zh-cndocsv27userprefacerequirements-check><a href=/zh-cn/docsv2.7/user/preface/requirements/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userprefacerequirements><span>需求</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userprefacearchitecture-li><input type=checkbox id=m-zh-cndocsv27userprefacearchitecture-check>
<label for=m-zh-cndocsv27userprefacearchitecture-check><a href=/zh-cn/docsv2.7/user/preface/architecture/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userprefacearchitecture><span>架构</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userprefaceusage-li><input type=checkbox id=m-zh-cndocsv27userprefaceusage-check>
<label for=m-zh-cndocsv27userprefaceusage-check><a href=/zh-cn/docsv2.7/user/preface/usage/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userprefaceusage><span>用法</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userquick-start-li><input type=checkbox id=m-zh-cndocsv27userquick-start-check>
<label for=m-zh-cndocsv27userquick-start-check><a href=/zh-cn/docsv2.7/user/quick-start/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userquick-start><span>快速开始</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userdependencies-li><input type=checkbox id=m-zh-cndocsv27userdependencies-check>
<label for=m-zh-cndocsv27userdependencies-check><a href=/zh-cn/docsv2.7/user/dependencies/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userdependencies><span>基本依赖</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27usermaturity-li><input type=checkbox id=m-zh-cndocsv27usermaturity-check>
<label for=m-zh-cndocsv27usermaturity-check><a href=/zh-cn/docsv2.7/user/maturity/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27usermaturity><span>成熟度</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userconfiguration-li><input type=checkbox id=m-zh-cndocsv27userconfiguration-check>
<label for=m-zh-cndocsv27userconfiguration-check><a href=/zh-cn/docsv2.7/user/configuration/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userconfiguration><span>配置手册</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationxml-li><input type=checkbox id=m-zh-cndocsv27userconfigurationxml-check>
<label for=m-zh-cndocsv27userconfigurationxml-check><a href=/zh-cn/docsv2.7/user/configuration/xml/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationxml><span>XML 配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationconfig-center-li><input type=checkbox id=m-zh-cndocsv27userconfigurationconfig-center-check>
<label for=m-zh-cndocsv27userconfigurationconfig-center-check><a href=/zh-cn/docsv2.7/user/configuration/config-center/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationconfig-center><span>动态配置中心</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationproperties-li><input type=checkbox id=m-zh-cndocsv27userconfigurationproperties-check>
<label for=m-zh-cndocsv27userconfigurationproperties-check><a href=/zh-cn/docsv2.7/user/configuration/properties/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationproperties><span>属性配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationenvironment-variables-li><input type=checkbox id=m-zh-cndocsv27userconfigurationenvironment-variables-check>
<label for=m-zh-cndocsv27userconfigurationenvironment-variables-check><a href=/zh-cn/docsv2.7/user/configuration/environment-variables/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationenvironment-variables><span>自动加载环境变量</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationapi-li><input type=checkbox id=m-zh-cndocsv27userconfigurationapi-check>
<label for=m-zh-cndocsv27userconfigurationapi-check><a href=/zh-cn/docsv2.7/user/configuration/api/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationapi><span>API 配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationannotation-li><input type=checkbox id=m-zh-cndocsv27userconfigurationannotation-check>
<label for=m-zh-cndocsv27userconfigurationannotation-check><a href=/zh-cn/docsv2.7/user/configuration/annotation/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationannotation><span>注解配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userconfigurationconfiguration-load-process-li><input type=checkbox id=m-zh-cndocsv27userconfigurationconfiguration-load-process-check>
<label for=m-zh-cndocsv27userconfigurationconfiguration-load-process-check><a href=/zh-cn/docsv2.7/user/configuration/configuration-load-process/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userconfigurationconfiguration-load-process><span>配置加载流程</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userexamples-li><input type=checkbox id=m-zh-cndocsv27userexamples-check>
<label for=m-zh-cndocsv27userexamples-check><a href=/zh-cn/docsv2.7/user/examples/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userexamples><span>用法示例</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplese9878de8af95e6aca1e695b0e9858de7bdae-li><input type=checkbox id=m-zh-cndocsv27userexamplese9878de8af95e6aca1e695b0e9858de7bdae-check>
<label for=m-zh-cndocsv27userexamplese9878de8af95e6aca1e695b0e9858de7bdae-check><a href=/zh-cn/docsv2.7/user/examples/%E9%87%8D%E8%AF%95%E6%AC%A1%E6%95%B0%E9%85%8D%E7%BD%AE/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplese9878de8af95e6aca1e695b0e9858de7bdae><span>服务重试</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplespreflight-check-li><input type=checkbox id=m-zh-cndocsv27userexamplespreflight-check-check>
<label for=m-zh-cndocsv27userexamplespreflight-check-check><a href=/zh-cn/docsv2.7/user/examples/preflight-check/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplespreflight-check><span>启动时检查</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesfault-tolerent-strategy-li><input type=checkbox id=m-zh-cndocsv27userexamplesfault-tolerent-strategy-check>
<label for=m-zh-cndocsv27userexamplesfault-tolerent-strategy-check><a href=/zh-cn/docsv2.7/user/examples/fault-tolerent-strategy/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesfault-tolerent-strategy><span>集群容错</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesloadbalance-li><input type=checkbox id=m-zh-cndocsv27userexamplesloadbalance-check>
<label for=m-zh-cndocsv27userexamplesloadbalance-check><a href=/zh-cn/docsv2.7/user/examples/loadbalance/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesloadbalance><span>负载均衡</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesthread-model-li><input type=checkbox id=m-zh-cndocsv27userexamplesthread-model-check>
<label for=m-zh-cndocsv27userexamplesthread-model-check><a href=/zh-cn/docsv2.7/user/examples/thread-model/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesthread-model><span>线程模型</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesexplicit-target-li><input type=checkbox id=m-zh-cndocsv27userexamplesexplicit-target-check>
<label for=m-zh-cndocsv27userexamplesexplicit-target-check><a href=/zh-cn/docsv2.7/user/examples/explicit-target/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesexplicit-target><span>直连提供者</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplessubscribe-only-li><input type=checkbox id=m-zh-cndocsv27userexamplessubscribe-only-check>
<label for=m-zh-cndocsv27userexamplessubscribe-only-check><a href=/zh-cn/docsv2.7/user/examples/subscribe-only/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplessubscribe-only><span>只订阅</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesmulti-protocols-li><input type=checkbox id=m-zh-cndocsv27userexamplesmulti-protocols-check>
<label for=m-zh-cndocsv27userexamplesmulti-protocols-check><a href=/zh-cn/docsv2.7/user/examples/multi-protocols/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesmulti-protocols><span>多协议</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesmulti-registry-li><input type=checkbox id=m-zh-cndocsv27userexamplesmulti-registry-check>
<label for=m-zh-cndocsv27userexamplesmulti-registry-check><a href=/zh-cn/docsv2.7/user/examples/multi-registry/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesmulti-registry><span>多注册中心</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesservice-group-li><input type=checkbox id=m-zh-cndocsv27userexamplesservice-group-check>
<label for=m-zh-cndocsv27userexamplesservice-group-check><a href=/zh-cn/docsv2.7/user/examples/service-group/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesservice-group><span>服务分组</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesstatic-service-li><input type=checkbox id=m-zh-cndocsv27userexamplesstatic-service-check>
<label for=m-zh-cndocsv27userexamplesstatic-service-check><a href=/zh-cn/docsv2.7/user/examples/static-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesstatic-service><span>静态服务</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesmulti-versions-li><input type=checkbox id=m-zh-cndocsv27userexamplesmulti-versions-check>
<label for=m-zh-cndocsv27userexamplesmulti-versions-check><a href=/zh-cn/docsv2.7/user/examples/multi-versions/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesmulti-versions><span>多版本</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesgroup-merger-li><input type=checkbox id=m-zh-cndocsv27userexamplesgroup-merger-check>
<label for=m-zh-cndocsv27userexamplesgroup-merger-check><a href=/zh-cn/docsv2.7/user/examples/group-merger/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesgroup-merger><span>分组聚合</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesparameter-validation-li><input type=checkbox id=m-zh-cndocsv27userexamplesparameter-validation-check>
<label for=m-zh-cndocsv27userexamplesparameter-validation-check><a href=/zh-cn/docsv2.7/user/examples/parameter-validation/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesparameter-validation><span>参数验证</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesgeneric-invoke-with-json-li><input type=checkbox id=m-zh-cndocsv27userexamplesgeneric-invoke-with-json-check>
<label for=m-zh-cndocsv27userexamplesgeneric-invoke-with-json-check><a href=/zh-cn/docsv2.7/user/examples/generic-invoke-with-json/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesgeneric-invoke-with-json><span>JSON泛化调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesmsgpack-serialization-li><input type=checkbox id=m-zh-cndocsv27userexamplesmsgpack-serialization-check>
<label for=m-zh-cndocsv27userexamplesmsgpack-serialization-check><a href=/zh-cn/docsv2.7/user/examples/msgpack-serialization/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesmsgpack-serialization><span>msgpack序列化</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesprovider-timeout-release-li><input type=checkbox id=m-zh-cndocsv27userexamplesprovider-timeout-release-check>
<label for=m-zh-cndocsv27userexamplesprovider-timeout-release-check><a href=/zh-cn/docsv2.7/user/examples/provider-timeout-release/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesprovider-timeout-release><span>provider超时打断</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesresult-cache-li><input type=checkbox id=m-zh-cndocsv27userexamplesresult-cache-check>
<label for=m-zh-cndocsv27userexamplesresult-cache-check><a href=/zh-cn/docsv2.7/user/examples/result-cache/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesresult-cache><span>结果缓存</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesbroadcast-resp-collect-li><input type=checkbox id=m-zh-cndocsv27userexamplesbroadcast-resp-collect-check>
<label for=m-zh-cndocsv27userexamplesbroadcast-resp-collect-check><a href=/zh-cn/docsv2.7/user/examples/broadcast-resp-collect/ title=收集Dubbo广播响应 class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesbroadcast-resp-collect><span>收集广播响应</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesinvoke-with-specified-ip-li><input type=checkbox id=m-zh-cndocsv27userexamplesinvoke-with-specified-ip-check>
<label for=m-zh-cndocsv27userexamplesinvoke-with-specified-ip-check><a href=/zh-cn/docsv2.7/user/examples/invoke-with-specified-ip/ title="指定Ip Port调用Provider" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesinvoke-with-specified-ip><span>指定IP</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesgeneric-reference-li><input type=checkbox id=m-zh-cndocsv27userexamplesgeneric-reference-check>
<label for=m-zh-cndocsv27userexamplesgeneric-reference-check><a href=/zh-cn/docsv2.7/user/examples/generic-reference/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesgeneric-reference><span>使用泛化调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesprotobuf-idl-li><input type=checkbox id=m-zh-cndocsv27userexamplesprotobuf-idl-check>
<label for=m-zh-cndocsv27userexamplesprotobuf-idl-check><a href=/zh-cn/docsv2.7/user/examples/protobuf-idl/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesprotobuf-idl><span>Protobuf</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplespb-generic-reference-li><input type=checkbox id=m-zh-cndocsv27userexamplespb-generic-reference-check>
<label for=m-zh-cndocsv27userexamplespb-generic-reference-check><a href=/zh-cn/docsv2.7/user/examples/pb-generic-reference/ title="GoogleProtobuf 对象泛化调用" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplespb-generic-reference><span>Protobuf 泛化调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesgeneric-service-li><input type=checkbox id=m-zh-cndocsv27userexamplesgeneric-service-check>
<label for=m-zh-cndocsv27userexamplesgeneric-service-check><a href=/zh-cn/docsv2.7/user/examples/generic-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesgeneric-service><span>实现泛化调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesecho-service-li><input type=checkbox id=m-zh-cndocsv27userexamplesecho-service-check>
<label for=m-zh-cndocsv27userexamplesecho-service-check><a href=/zh-cn/docsv2.7/user/examples/echo-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesecho-service><span>回声测试</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplescontext-li><input type=checkbox id=m-zh-cndocsv27userexamplescontext-check>
<label for=m-zh-cndocsv27userexamplescontext-check><a href=/zh-cn/docsv2.7/user/examples/context/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplescontext><span>上下文信息</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesattachment-li><input type=checkbox id=m-zh-cndocsv27userexamplesattachment-check>
<label for=m-zh-cndocsv27userexamplesattachment-check><a href=/zh-cn/docsv2.7/user/examples/attachment/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesattachment><span>隐式参数</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesasync-call-li><input type=checkbox id=m-zh-cndocsv27userexamplesasync-call-check>
<label for=m-zh-cndocsv27userexamplesasync-call-check><a href=/zh-cn/docsv2.7/user/examples/async-call/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesasync-call><span>异步调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesasync-execute-on-provider-li><input type=checkbox id=m-zh-cndocsv27userexamplesasync-execute-on-provider-check>
<label for=m-zh-cndocsv27userexamplesasync-execute-on-provider-check><a href=/zh-cn/docsv2.7/user/examples/async-execute-on-provider/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesasync-execute-on-provider><span>异步执行</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexampleslocal-call-li><input type=checkbox id=m-zh-cndocsv27userexampleslocal-call-check>
<label for=m-zh-cndocsv27userexampleslocal-call-check><a href=/zh-cn/docsv2.7/user/examples/local-call/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexampleslocal-call><span>本地调用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplescallback-parameter-li><input type=checkbox id=m-zh-cndocsv27userexamplescallback-parameter-check>
<label for=m-zh-cndocsv27userexamplescallback-parameter-check><a href=/zh-cn/docsv2.7/user/examples/callback-parameter/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplescallback-parameter><span>参数回调</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesevents-notify-li><input type=checkbox id=m-zh-cndocsv27userexamplesevents-notify-check>
<label for=m-zh-cndocsv27userexamplesevents-notify-check><a href=/zh-cn/docsv2.7/user/examples/events-notify/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesevents-notify><span>事件通知</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexampleslocal-stub-li><input type=checkbox id=m-zh-cndocsv27userexampleslocal-stub-check>
<label for=m-zh-cndocsv27userexampleslocal-stub-check><a href=/zh-cn/docsv2.7/user/examples/local-stub/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexampleslocal-stub><span>本地存根</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexampleslocal-mock-li><input type=checkbox id=m-zh-cndocsv27userexampleslocal-mock-check>
<label for=m-zh-cndocsv27userexampleslocal-mock-check><a href=/zh-cn/docsv2.7/user/examples/local-mock/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexampleslocal-mock><span>本地伪装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesdelay-publish-li><input type=checkbox id=m-zh-cndocsv27userexamplesdelay-publish-check>
<label for=m-zh-cndocsv27userexamplesdelay-publish-check><a href=/zh-cn/docsv2.7/user/examples/delay-publish/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesdelay-publish><span>延迟暴露</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesconcurrency-control-li><input type=checkbox id=m-zh-cndocsv27userexamplesconcurrency-control-check>
<label for=m-zh-cndocsv27userexamplesconcurrency-control-check><a href=/zh-cn/docsv2.7/user/examples/concurrency-control/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesconcurrency-control><span>并发控制</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesconfig-connections-li><input type=checkbox id=m-zh-cndocsv27userexamplesconfig-connections-check>
<label for=m-zh-cndocsv27userexamplesconfig-connections-check><a href=/zh-cn/docsv2.7/user/examples/config-connections/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesconfig-connections><span>连接控制</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexampleslazy-connect-li><input type=checkbox id=m-zh-cndocsv27userexampleslazy-connect-check>
<label for=m-zh-cndocsv27userexampleslazy-connect-check><a href=/zh-cn/docsv2.7/user/examples/lazy-connect/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexampleslazy-connect><span>延迟连接</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesstickiness-li><input type=checkbox id=m-zh-cndocsv27userexamplesstickiness-check>
<label for=m-zh-cndocsv27userexamplesstickiness-check><a href=/zh-cn/docsv2.7/user/examples/stickiness/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesstickiness><span>粘滞连接</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplestls-li><input type=checkbox id=m-zh-cndocsv27userexamplestls-check>
<label for=m-zh-cndocsv27userexamplestls-check><a href=/zh-cn/docsv2.7/user/examples/tls/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplestls><span>TLS</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplestoken-authorization-li><input type=checkbox id=m-zh-cndocsv27userexamplestoken-authorization-check>
<label for=m-zh-cndocsv27userexamplestoken-authorization-check><a href=/zh-cn/docsv2.7/user/examples/token-authorization/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplestoken-authorization><span>令牌验证</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesrouting-rule-li><input type=checkbox id=m-zh-cndocsv27userexamplesrouting-rule-check>
<label for=m-zh-cndocsv27userexamplesrouting-rule-check><a href=/zh-cn/docsv2.7/user/examples/routing-rule/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesrouting-rule><span>路由规则</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesrouting-rule-deprecated-li><input type=checkbox id=m-zh-cndocsv27userexamplesrouting-rule-deprecated-check>
<label for=m-zh-cndocsv27userexamplesrouting-rule-deprecated-check><a href=/zh-cn/docsv2.7/user/examples/routing-rule-deprecated/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesrouting-rule-deprecated><span>旧路由规则</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesconfig-rule-li><input type=checkbox id=m-zh-cndocsv27userexamplesconfig-rule-check>
<label for=m-zh-cndocsv27userexamplesconfig-rule-check><a href=/zh-cn/docsv2.7/user/examples/config-rule/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesconfig-rule><span>配置规则</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesservice-downgrade-li><input type=checkbox id=m-zh-cndocsv27userexamplesservice-downgrade-check>
<label for=m-zh-cndocsv27userexamplesservice-downgrade-check><a href=/zh-cn/docsv2.7/user/examples/service-downgrade/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesservice-downgrade><span>服务降级</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesconfig-rule-deprecated-li><input type=checkbox id=m-zh-cndocsv27userexamplesconfig-rule-deprecated-check>
<label for=m-zh-cndocsv27userexamplesconfig-rule-deprecated-check><a href=/zh-cn/docsv2.7/user/examples/config-rule-deprecated/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesconfig-rule-deprecated><span>旧配置规则</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesconsumer-threadpool-li><input type=checkbox id=m-zh-cndocsv27userexamplesconsumer-threadpool-check>
<label for=m-zh-cndocsv27userexamplesconsumer-threadpool-check><a href=/zh-cn/docsv2.7/user/examples/consumer-threadpool/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesconsumer-threadpool><span>消费端线程池模型</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesgraceful-shutdown-li><input type=checkbox id=m-zh-cndocsv27userexamplesgraceful-shutdown-check>
<label for=m-zh-cndocsv27userexamplesgraceful-shutdown-check><a href=/zh-cn/docsv2.7/user/examples/graceful-shutdown/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesgraceful-shutdown><span>优雅停机</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexampleshostname-binding-li><input type=checkbox id=m-zh-cndocsv27userexampleshostname-binding-check>
<label for=m-zh-cndocsv27userexampleshostname-binding-check><a href=/zh-cn/docsv2.7/user/examples/hostname-binding/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexampleshostname-binding><span>主机绑定</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userexamplesset-host-li><input type=checkbox id=m-zh-cndocsv27userexamplesset-host-check>
<label for=m-zh-cndocsv27userexamplesset-host-check><a href=/zh-cn/docsv2.7/user/examples/set-host/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userexamplesset-host><span>主机配置</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userreferences-li><input type=checkbox id=m-zh-cndocsv27userreferences-check>
<label for=m-zh-cndocsv27userreferences-check><a href=/zh-cn/docsv2.7/user/references/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userreferences><span>参考手册</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userreferencesxml-li><input type=checkbox id=m-zh-cndocsv27userreferencesxml-check>
<label for=m-zh-cndocsv27userreferencesxml-check><a href=/zh-cn/docsv2.7/user/references/xml/ title="Schema 配置参考手册" class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userreferencesxml><span>XML 配置</span></a></label><ul class="ul-4 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-application-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-application-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-application-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-application/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-application><span>dubbo:application</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-argument-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-argument-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-argument-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-argument/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-argument><span>dubbo:argument</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-config-center-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-config-center-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-config-center-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-config-center/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-config-center><span>dubbo:config-center</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-consumer-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-consumer-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-consumer-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-consumer/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-consumer><span>dubbo:consumer</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-method-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-method-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-method-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-method/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-method><span>dubbo:method</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-module-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-module-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-module-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-module/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-module><span>dubbo:module</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-monitor-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-monitor-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-monitor-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-monitor/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-monitor><span>dubbo:monitor</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-parameter-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-parameter-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-parameter-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-parameter/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-parameter><span>dubbo:parameter</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-protocol-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-protocol-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-protocol-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-protocol/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-protocol><span>dubbo:protocol</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-provider-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-provider-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-provider-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-provider/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-provider><span>dubbo:provider</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-reference-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-reference-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-reference-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-reference/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-reference><span>dubbo:reference</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-registry-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-registry-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-registry-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-registry/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-registry><span>dubbo:registry</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesxmldubbo-service-li><input type=checkbox id=m-zh-cndocsv27userreferencesxmldubbo-service-check>
<label for=m-zh-cndocsv27userreferencesxmldubbo-service-check><a href=/zh-cn/docsv2.7/user/references/xml/dubbo-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesxmldubbo-service><span>dubbo:service</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userreferencesprotocol-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocol-check>
<label for=m-zh-cndocsv27userreferencesprotocol-check><a href=/zh-cn/docsv2.7/user/references/protocol/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userreferencesprotocol><span>协议参考手册</span></a></label><ul class="ul-4 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocoldubbo-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocoldubbo-check>
<label for=m-zh-cndocsv27userreferencesprotocoldubbo-check><a href=/zh-cn/docsv2.7/user/references/protocol/dubbo/ title="dubbo 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocoldubbo><span>dubbo://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolrest-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolrest-check>
<label for=m-zh-cndocsv27userreferencesprotocolrest-check><a href=/zh-cn/docsv2.7/user/references/protocol/rest/ title="rest 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolrest><span>rest://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolhttp-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolhttp-check>
<label for=m-zh-cndocsv27userreferencesprotocolhttp-check><a href=/zh-cn/docsv2.7/user/references/protocol/http/ title="http 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolhttp><span>http://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolhessian-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolhessian-check>
<label for=m-zh-cndocsv27userreferencesprotocolhessian-check><a href=/zh-cn/docsv2.7/user/references/protocol/hessian/ title="hessian 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolhessian><span>hessian://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolredis-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolredis-check>
<label for=m-zh-cndocsv27userreferencesprotocolredis-check><a href=/zh-cn/docsv2.7/user/references/protocol/redis/ title="redis 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolredis><span>redis://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolthrift-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolthrift-check>
<label for=m-zh-cndocsv27userreferencesprotocolthrift-check><a href=/zh-cn/docsv2.7/user/references/protocol/thrift/ title="thrift 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolthrift><span>thrift://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolgrpc-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolgrpc-check>
<label for=m-zh-cndocsv27userreferencesprotocolgrpc-check><a href=/zh-cn/docsv2.7/user/references/protocol/grpc/ title="gRPC 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolgrpc><span>grpc://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolmemcached-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolmemcached-check>
<label for=m-zh-cndocsv27userreferencesprotocolmemcached-check><a href=/zh-cn/docsv2.7/user/references/protocol/memcached/ title="memcached 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolmemcached><span>memcached://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolrmi-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolrmi-check>
<label for=m-zh-cndocsv27userreferencesprotocolrmi-check><a href=/zh-cn/docsv2.7/user/references/protocol/rmi/ title="rmi 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolrmi><span>rmi://</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesprotocolwebservice-li><input type=checkbox id=m-zh-cndocsv27userreferencesprotocolwebservice-check>
<label for=m-zh-cndocsv27userreferencesprotocolwebservice-check><a href=/zh-cn/docsv2.7/user/references/protocol/webservice/ title="webservice 协议" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesprotocolwebservice><span>webservice://</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userreferencesregistry-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistry-check>
<label for=m-zh-cndocsv27userreferencesregistry-check><a href=/zh-cn/docsv2.7/user/references/registry/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userreferencesregistry><span>注册中心参考手册</span></a></label><ul class="ul-4 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesregistrynacos-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistrynacos-check>
<label for=m-zh-cndocsv27userreferencesregistrynacos-check><a href=/zh-cn/docsv2.7/user/references/registry/nacos/ title="Nacos 注册中心" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesregistrynacos><span>Nacos</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesregistryzookeeper-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistryzookeeper-check>
<label for=m-zh-cndocsv27userreferencesregistryzookeeper-check><a href=/zh-cn/docsv2.7/user/references/registry/zookeeper/ title="Zookeeper 注册中心" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesregistryzookeeper><span>Zookeeper</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesregistrymulticast-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistrymulticast-check>
<label for=m-zh-cndocsv27userreferencesregistrymulticast-check><a href=/zh-cn/docsv2.7/user/references/registry/multicast/ title="Multicast 注册中心" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesregistrymulticast><span>Multicast</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesregistryredis-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistryredis-check>
<label for=m-zh-cndocsv27userreferencesregistryredis-check><a href=/zh-cn/docsv2.7/user/references/registry/redis/ title="Redis 注册中心" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesregistryredis><span>Redis</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesregistrysimple-li><input type=checkbox id=m-zh-cndocsv27userreferencesregistrysimple-check>
<label for=m-zh-cndocsv27userreferencesregistrysimple-check><a href=/zh-cn/docsv2.7/user/references/registry/simple/ title="Simple 注册中心" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesregistrysimple><span>Simple</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesmetadata-li><input type=checkbox id=m-zh-cndocsv27userreferencesmetadata-check>
<label for=m-zh-cndocsv27userreferencesmetadata-check><a href=/zh-cn/docsv2.7/user/references/metadata/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesmetadata><span>元数据参考手册</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesapi-li><input type=checkbox id=m-zh-cndocsv27userreferencesapi-check>
<label for=m-zh-cndocsv27userreferencesapi-check><a href=/zh-cn/docsv2.7/user/references/api/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesapi><span>API 参考手册</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesqos-li><input type=checkbox id=m-zh-cndocsv27userreferencesqos-check>
<label for=m-zh-cndocsv27userreferencesqos-check><a href=/zh-cn/docsv2.7/user/references/qos/ title=在线运维命令参考手册 class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesqos><span>QOS 手册</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencestelnet-li><input type=checkbox id=m-zh-cndocsv27userreferencestelnet-check>
<label for=m-zh-cndocsv27userreferencestelnet-check><a href=/zh-cn/docsv2.7/user/references/telnet/ title="Telnet 命令参考手册" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencestelnet><span>Telnet 手册</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userreferencesmaven-li><input type=checkbox id=m-zh-cndocsv27userreferencesmaven-check>
<label for=m-zh-cndocsv27userreferencesmaven-check><a href=/zh-cn/docsv2.7/user/references/maven/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userreferencesmaven><span>Maven 插件参考手册</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userversions-li><input type=checkbox id=m-zh-cndocsv27userversions-check>
<label for=m-zh-cndocsv27userversions-check><a href=/zh-cn/docsv2.7/user/versions/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userversions><span>版本升级</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userversionsversion-270-li><input type=checkbox id=m-zh-cndocsv27userversionsversion-270-check>
<label for=m-zh-cndocsv27userversionsversion-270-check><a href=/zh-cn/docsv2.7/user/versions/version-270/ title=升级与可能的兼容性问题总结 class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userversionsversion-270><span>2.7.0</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userbest-practice-li><input type=checkbox id=m-zh-cndocsv27userbest-practice-check>
<label for=m-zh-cndocsv27userbest-practice-check><a href=/zh-cn/docsv2.7/user/best-practice/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userbest-practice><span>服务化最佳实践</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userrecommend-li><input type=checkbox id=m-zh-cndocsv27userrecommend-check>
<label for=m-zh-cndocsv27userrecommend-check><a href=/zh-cn/docsv2.7/user/recommend/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userrecommend><span>推荐用法</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27usercapacity-plan-li><input type=checkbox id=m-zh-cndocsv27usercapacity-plan-check>
<label for=m-zh-cndocsv27usercapacity-plan-check><a href=/zh-cn/docsv2.7/user/capacity-plan/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27usercapacity-plan><span>容量规划</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userperf-test-li><input type=checkbox id=m-zh-cndocsv27userperf-test-check>
<label for=m-zh-cndocsv27userperf-test-check><a href=/zh-cn/docsv2.7/user/perf-test/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userperf-test><span>性能测试报告</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27usercoveragence-li><input type=checkbox id=m-zh-cndocsv27usercoveragence-check>
<label for=m-zh-cndocsv27usercoveragence-check><a href=/zh-cn/docsv2.7/user/coveragence/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27usercoveragence><span>测试覆盖率报告</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userbenchmark-tool-li><input type=checkbox id=m-zh-cndocsv27userbenchmark-tool-check>
<label for=m-zh-cndocsv27userbenchmark-tool-check><a href=/zh-cn/docsv2.7/user/benchmark-tool/ title=基准测试工具包 class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userbenchmark-tool><span>基准测试</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userrest-li><input type=checkbox id=m-zh-cndocsv27userrest-check>
<label for=m-zh-cndocsv27userrest-check><a href=/zh-cn/docsv2.7/user/rest/ title="开发 REST 应用" class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userrest><span>REST 支持</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27usersimple-monitor-li><input type=checkbox id=m-zh-cndocsv27usersimple-monitor-check>
<label for=m-zh-cndocsv27usersimple-monitor-check><a href=/zh-cn/docsv2.7/user/simple-monitor/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27usersimple-monitor><span>简单监控</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userserialization-li><input type=checkbox id=m-zh-cndocsv27userserialization-check>
<label for=m-zh-cndocsv27userserialization-check><a href=/zh-cn/docsv2.7/user/serialization/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userserialization><span>Kryo 和 FST 序列化</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userlanguages-li><input type=checkbox id=m-zh-cndocsv27userlanguages-check>
<label for=m-zh-cndocsv27userlanguages-check><a href=/zh-cn/docsv2.7/user/languages/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userlanguages><span>其他语言支持</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27userlanguageserlang-li><input type=checkbox id=m-zh-cndocsv27userlanguageserlang-check>
<label for=m-zh-cndocsv27userlanguageserlang-check><a href=/zh-cn/docsv2.7/user/languages/erlang/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27userlanguageserlang><span>Erlang</span></a></label><ul class="ul-4 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userlanguageserlangquick-start-li><input type=checkbox id=m-zh-cndocsv27userlanguageserlangquick-start-check>
<label for=m-zh-cndocsv27userlanguageserlangquick-start-check><a href=/zh-cn/docsv2.7/user/languages/erlang/quick-start/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userlanguageserlangquick-start><span>快速开始</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userlanguageserlangreference-li><input type=checkbox id=m-zh-cndocsv27userlanguageserlangreference-check>
<label for=m-zh-cndocsv27userlanguageserlangreference-check><a href=/zh-cn/docsv2.7/user/languages/erlang/reference/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userlanguageserlangreference><span>消费者配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userlanguageserlangservice-li><input type=checkbox id=m-zh-cndocsv27userlanguageserlangservice-check>
<label for=m-zh-cndocsv27userlanguageserlangservice-check><a href=/zh-cn/docsv2.7/user/languages/erlang/service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userlanguageserlangservice><span>提供者配置</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27userlanguageserlangserialization-li><input type=checkbox id=m-zh-cndocsv27userlanguageserlangserialization-check>
<label for=m-zh-cndocsv27userlanguageserlangserialization-check><a href=/zh-cn/docsv2.7/user/languages/erlang/serialization/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27userlanguageserlangserialization><span>序列化配置项</span></a></label></li></ul></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27usernew-features-in-a-glance-li><input type=checkbox id=m-zh-cndocsv27usernew-features-in-a-glance-check>
<label for=m-zh-cndocsv27usernew-features-in-a-glance-check><a href=/zh-cn/docsv2.7/user/new-features-in-a-glance/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27usernew-features-in-a-glance><span></span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27dev-li><input type=checkbox id=m-zh-cndocsv27dev-check>
<label for=m-zh-cndocsv27dev-check><a href=/zh-cn/docsv2.7/dev/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27dev><span>开发指南</span></a></label><ul class="ul-2 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devbuild-li><input type=checkbox id=m-zh-cndocsv27devbuild-check>
<label for=m-zh-cndocsv27devbuild-check><a href=/zh-cn/docsv2.7/dev/build/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devbuild><span>源码构建</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devdesign-li><input type=checkbox id=m-zh-cndocsv27devdesign-check>
<label for=m-zh-cndocsv27devdesign-check><a href=/zh-cn/docsv2.7/dev/design/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devdesign><span>框架设计</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devspi-li><input type=checkbox id=m-zh-cndocsv27devspi-check>
<label for=m-zh-cndocsv27devspi-check><a href=/zh-cn/docsv2.7/dev/spi/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devspi><span>扩展点加载</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplementation-li><input type=checkbox id=m-zh-cndocsv27devimplementation-check>
<label for=m-zh-cndocsv27devimplementation-check><a href=/zh-cn/docsv2.7/dev/implementation/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplementation><span>实现细节</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27devimpls-li><input type=checkbox id=m-zh-cndocsv27devimpls-check>
<label for=m-zh-cndocsv27devimpls-check><a href=/zh-cn/docsv2.7/dev/impls/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27devimpls><span>SPI 扩展实现</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsprotocol-li><input type=checkbox id=m-zh-cndocsv27devimplsprotocol-check>
<label for=m-zh-cndocsv27devimplsprotocol-check><a href=/zh-cn/docsv2.7/dev/impls/protocol/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsprotocol><span>协议扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsfilter-li><input type=checkbox id=m-zh-cndocsv27devimplsfilter-check>
<label for=m-zh-cndocsv27devimplsfilter-check><a href=/zh-cn/docsv2.7/dev/impls/filter/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsfilter><span>调用拦截扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsinvoker-listener-li><input type=checkbox id=m-zh-cndocsv27devimplsinvoker-listener-check>
<label for=m-zh-cndocsv27devimplsinvoker-listener-check><a href=/zh-cn/docsv2.7/dev/impls/invoker-listener/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsinvoker-listener><span>引用监听扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsexporter-listener-li><input type=checkbox id=m-zh-cndocsv27devimplsexporter-listener-check>
<label for=m-zh-cndocsv27devimplsexporter-listener-check><a href=/zh-cn/docsv2.7/dev/impls/exporter-listener/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsexporter-listener><span>暴露监听扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplscluster-li><input type=checkbox id=m-zh-cndocsv27devimplscluster-check>
<label for=m-zh-cndocsv27devimplscluster-check><a href=/zh-cn/docsv2.7/dev/impls/cluster/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplscluster><span>集群扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsrouter-li><input type=checkbox id=m-zh-cndocsv27devimplsrouter-check>
<label for=m-zh-cndocsv27devimplsrouter-check><a href=/zh-cn/docsv2.7/dev/impls/router/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsrouter><span>路由扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsload-balance-li><input type=checkbox id=m-zh-cndocsv27devimplsload-balance-check>
<label for=m-zh-cndocsv27devimplsload-balance-check><a href=/zh-cn/docsv2.7/dev/impls/load-balance/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsload-balance><span>负载均衡扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsmerger-li><input type=checkbox id=m-zh-cndocsv27devimplsmerger-check>
<label for=m-zh-cndocsv27devimplsmerger-check><a href=/zh-cn/docsv2.7/dev/impls/merger/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsmerger><span>合并结果扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsregistry-li><input type=checkbox id=m-zh-cndocsv27devimplsregistry-check>
<label for=m-zh-cndocsv27devimplsregistry-check><a href=/zh-cn/docsv2.7/dev/impls/registry/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsregistry><span>注册中心扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsmonitor-li><input type=checkbox id=m-zh-cndocsv27devimplsmonitor-check>
<label for=m-zh-cndocsv27devimplsmonitor-check><a href=/zh-cn/docsv2.7/dev/impls/monitor/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsmonitor><span>监控中心扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsextension-factory-li><input type=checkbox id=m-zh-cndocsv27devimplsextension-factory-check>
<label for=m-zh-cndocsv27devimplsextension-factory-check><a href=/zh-cn/docsv2.7/dev/impls/extension-factory/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsextension-factory><span>扩展点加载扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsproxy-factory-li><input type=checkbox id=m-zh-cndocsv27devimplsproxy-factory-check>
<label for=m-zh-cndocsv27devimplsproxy-factory-check><a href=/zh-cn/docsv2.7/dev/impls/proxy-factory/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsproxy-factory><span>动态代理扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplscompiler-li><input type=checkbox id=m-zh-cndocsv27devimplscompiler-check>
<label for=m-zh-cndocsv27devimplscompiler-check><a href=/zh-cn/docsv2.7/dev/impls/compiler/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplscompiler><span>编译器扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsconfig-center-li><input type=checkbox id=m-zh-cndocsv27devimplsconfig-center-check>
<label for=m-zh-cndocsv27devimplsconfig-center-check><a href=/zh-cn/docsv2.7/dev/impls/config-center/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsconfig-center><span>配置中心扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsdispatcher-li><input type=checkbox id=m-zh-cndocsv27devimplsdispatcher-check>
<label for=m-zh-cndocsv27devimplsdispatcher-check><a href=/zh-cn/docsv2.7/dev/impls/dispatcher/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsdispatcher><span>消息派发扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsthreadpool-li><input type=checkbox id=m-zh-cndocsv27devimplsthreadpool-check>
<label for=m-zh-cndocsv27devimplsthreadpool-check><a href=/zh-cn/docsv2.7/dev/impls/threadpool/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsthreadpool><span>线程池扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsserialize-li><input type=checkbox id=m-zh-cndocsv27devimplsserialize-check>
<label for=m-zh-cndocsv27devimplsserialize-check><a href=/zh-cn/docsv2.7/dev/impls/serialize/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsserialize><span>序列化扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsremoting-li><input type=checkbox id=m-zh-cndocsv27devimplsremoting-check>
<label for=m-zh-cndocsv27devimplsremoting-check><a href=/zh-cn/docsv2.7/dev/impls/remoting/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsremoting><span>网络传输扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsexchanger-li><input type=checkbox id=m-zh-cndocsv27devimplsexchanger-check>
<label for=m-zh-cndocsv27devimplsexchanger-check><a href=/zh-cn/docsv2.7/dev/impls/exchanger/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsexchanger><span>信息交换扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsnetworker-li><input type=checkbox id=m-zh-cndocsv27devimplsnetworker-check>
<label for=m-zh-cndocsv27devimplsnetworker-check><a href=/zh-cn/docsv2.7/dev/impls/networker/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsnetworker><span>组网扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplstelnet-handler-li><input type=checkbox id=m-zh-cndocsv27devimplstelnet-handler-check>
<label for=m-zh-cndocsv27devimplstelnet-handler-check><a href=/zh-cn/docsv2.7/dev/impls/telnet-handler/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplstelnet-handler><span>Telnet 命令扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsstatus-checker-li><input type=checkbox id=m-zh-cndocsv27devimplsstatus-checker-check>
<label for=m-zh-cndocsv27devimplsstatus-checker-check><a href=/zh-cn/docsv2.7/dev/impls/status-checker/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsstatus-checker><span>状态检查扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplscontainer-li><input type=checkbox id=m-zh-cndocsv27devimplscontainer-check>
<label for=m-zh-cndocsv27devimplscontainer-check><a href=/zh-cn/docsv2.7/dev/impls/container/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplscontainer><span>容器扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplscache-li><input type=checkbox id=m-zh-cndocsv27devimplscache-check>
<label for=m-zh-cndocsv27devimplscache-check><a href=/zh-cn/docsv2.7/dev/impls/cache/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplscache><span>缓存扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplsvalidation-li><input type=checkbox id=m-zh-cndocsv27devimplsvalidation-check>
<label for=m-zh-cndocsv27devimplsvalidation-check><a href=/zh-cn/docsv2.7/dev/impls/validation/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplsvalidation><span>验证扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplslogger-adapter-li><input type=checkbox id=m-zh-cndocsv27devimplslogger-adapter-check>
<label for=m-zh-cndocsv27devimplslogger-adapter-check><a href=/zh-cn/docsv2.7/dev/impls/logger-adapter/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplslogger-adapter><span>日志适配扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devimplspage-li><input type=checkbox id=m-zh-cndocsv27devimplspage-check>
<label for=m-zh-cndocsv27devimplspage-check><a href=/zh-cn/docsv2.7/dev/impls/page/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devimplspage><span></span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27devprincipals-li><input type=checkbox id=m-zh-cndocsv27devprincipals-check>
<label for=m-zh-cndocsv27devprincipals-check><a href=/zh-cn/docsv2.7/dev/principals/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27devprincipals><span>设计原则</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalscode-detail-li><input type=checkbox id=m-zh-cndocsv27devprincipalscode-detail-check>
<label for=m-zh-cndocsv27devprincipalscode-detail-check><a href=/zh-cn/docsv2.7/dev/principals/code-detail/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalscode-detail><span>魔鬼在细节</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsconfiguration-li><input type=checkbox id=m-zh-cndocsv27devprincipalsconfiguration-check>
<label for=m-zh-cndocsv27devprincipalsconfiguration-check><a href=/zh-cn/docsv2.7/dev/principals/configuration/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsconfiguration><span>配置设计</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsdummy-li><input type=checkbox id=m-zh-cndocsv27devprincipalsdummy-check>
<label for=m-zh-cndocsv27devprincipalsdummy-check><a href=/zh-cn/docsv2.7/dev/principals/dummy/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsdummy><span>防痴呆设计</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsexpansibility-li><input type=checkbox id=m-zh-cndocsv27devprincipalsexpansibility-check>
<label for=m-zh-cndocsv27devprincipalsexpansibility-check><a href=/zh-cn/docsv2.7/dev/principals/expansibility/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsexpansibility><span>谈谈扩充式扩展与增量式扩展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsextension-li><input type=checkbox id=m-zh-cndocsv27devprincipalsextension-check>
<label for=m-zh-cndocsv27devprincipalsextension-check><a href=/zh-cn/docsv2.7/dev/principals/extension/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsextension><span>扩展点重构</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsgeneral-knowledge-li><input type=checkbox id=m-zh-cndocsv27devprincipalsgeneral-knowledge-check>
<label for=m-zh-cndocsv27devprincipalsgeneral-knowledge-check><a href=/zh-cn/docsv2.7/dev/principals/general-knowledge/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsgeneral-knowledge><span>一些设计上的基本常识</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsrobustness-li><input type=checkbox id=m-zh-cndocsv27devprincipalsrobustness-check>
<label for=m-zh-cndocsv27devprincipalsrobustness-check><a href=/zh-cn/docsv2.7/dev/principals/robustness/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsrobustness><span>设计实现的健壮性</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devprincipalsintroduction-li><input type=checkbox id=m-zh-cndocsv27devprincipalsintroduction-check>
<label for=m-zh-cndocsv27devprincipalsintroduction-check><a href=/zh-cn/docsv2.7/dev/principals/introduction/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devprincipalsintroduction><span></span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devcontract-li><input type=checkbox id=m-zh-cndocsv27devcontract-check>
<label for=m-zh-cndocsv27devcontract-check><a href=/zh-cn/docsv2.7/dev/contract/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devcontract><span>公共契约</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27devsource-li><input type=checkbox id=m-zh-cndocsv27devsource-check>
<label for=m-zh-cndocsv27devsource-check><a href=/zh-cn/docsv2.7/dev/source/ title="Dubbo 源代码分析" class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27devsource><span>源代码</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourcedubbo-spi-li><input type=checkbox id=m-zh-cndocsv27devsourcedubbo-spi-check>
<label for=m-zh-cndocsv27devsourcedubbo-spi-check><a href=/zh-cn/docsv2.7/dev/source/dubbo-spi/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourcedubbo-spi><span>Dubbo SPI</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourcerouter-li><input type=checkbox id=m-zh-cndocsv27devsourcerouter-check>
<label for=m-zh-cndocsv27devsourcerouter-check><a href=/zh-cn/docsv2.7/dev/source/router/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourcerouter><span>服务路由</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourceadaptive-extension-li><input type=checkbox id=m-zh-cndocsv27devsourceadaptive-extension-check>
<label for=m-zh-cndocsv27devsourceadaptive-extension-check><a href=/zh-cn/docsv2.7/dev/source/adaptive-extension/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourceadaptive-extension><span>SPI 自适应拓展</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourceexport-service-li><input type=checkbox id=m-zh-cndocsv27devsourceexport-service-check>
<label for=m-zh-cndocsv27devsourceexport-service-check><a href=/zh-cn/docsv2.7/dev/source/export-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourceexport-service><span>服务导出</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourcerefer-service-li><input type=checkbox id=m-zh-cndocsv27devsourcerefer-service-check>
<label for=m-zh-cndocsv27devsourcerefer-service-check><a href=/zh-cn/docsv2.7/dev/source/refer-service/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourcerefer-service><span>服务引用</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourceservice-invoking-process-li><input type=checkbox id=m-zh-cndocsv27devsourceservice-invoking-process-check>
<label for=m-zh-cndocsv27devsourceservice-invoking-process-check><a href=/zh-cn/docsv2.7/dev/source/service-invoking-process/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourceservice-invoking-process><span>服务调用过程</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourcedirectory-li><input type=checkbox id=m-zh-cndocsv27devsourcedirectory-check>
<label for=m-zh-cndocsv27devsourcedirectory-check><a href=/zh-cn/docsv2.7/dev/source/directory/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourcedirectory><span>服务目录</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourcecluster-li><input type=checkbox id=m-zh-cndocsv27devsourcecluster-check>
<label for=m-zh-cndocsv27devsourcecluster-check><a href=/zh-cn/docsv2.7/dev/source/cluster/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourcecluster><span>集群</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devsourceloadbalance-li><input type=checkbox id=m-zh-cndocsv27devsourceloadbalance-check>
<label for=m-zh-cndocsv27devsourceloadbalance-check><a href=/zh-cn/docsv2.7/dev/source/loadbalance/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devsourceloadbalance><span>负载均衡</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devrelease-li><input type=checkbox id=m-zh-cndocsv27devrelease-check>
<label for=m-zh-cndocsv27devrelease-check><a href=/zh-cn/docsv2.7/dev/release/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devrelease><span>版本管理</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devchecklist-li><input type=checkbox id=m-zh-cndocsv27devchecklist-check>
<label for=m-zh-cndocsv27devchecklist-check><a href=/zh-cn/docsv2.7/dev/checklist/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devchecklist><span>检查列表</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devcoding-li><input type=checkbox id=m-zh-cndocsv27devcoding-check>
<label for=m-zh-cndocsv27devcoding-check><a href=/zh-cn/docsv2.7/dev/coding/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devcoding><span>编码约定</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devcode-smell-li><input type=checkbox id=m-zh-cndocsv27devcode-smell-check>
<label for=m-zh-cndocsv27devcode-smell-check><a href=/zh-cn/docsv2.7/dev/code-smell/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devcode-smell><span>坏味道</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27devtck-li><input type=checkbox id=m-zh-cndocsv27devtck-check>
<label for=m-zh-cndocsv27devtck-check><a href=/zh-cn/docsv2.7/dev/tck/ title=技术兼容性测试 class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27devtck><span>TCK</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27admin-li><input type=checkbox id=m-zh-cndocsv27admin-check>
<label for=m-zh-cndocsv27admin-check><a href=/zh-cn/docsv2.7/admin/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27admin><span>运维指南</span></a></label><ul class="ul-2 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27adminops-li><input type=checkbox id=m-zh-cndocsv27adminops-check>
<label for=m-zh-cndocsv27adminops-check><a href=/zh-cn/docsv2.7/admin/ops/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27adminops><span>Dubbo Admin 运维指南</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopsfunctions-li><input type=checkbox id=m-zh-cndocsv27adminopsfunctions-check>
<label for=m-zh-cndocsv27adminopsfunctions-check><a href=/zh-cn/docsv2.7/admin/ops/functions/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopsfunctions><span>管理控制台运维</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopsintroduction-li><input type=checkbox id=m-zh-cndocsv27adminopsintroduction-check>
<label for=m-zh-cndocsv27adminopsintroduction-check><a href=/zh-cn/docsv2.7/admin/ops/introduction/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopsintroduction><span>Dubbo 管理控制台介绍</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopstest-li><input type=checkbox id=m-zh-cndocsv27adminopstest-check>
<label for=m-zh-cndocsv27adminopstest-check><a href=/zh-cn/docsv2.7/admin/ops/test/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopstest><span>服务测试</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopssearch-li><input type=checkbox id=m-zh-cndocsv27adminopssearch-check>
<label for=m-zh-cndocsv27adminopssearch-check><a href=/zh-cn/docsv2.7/admin/ops/search/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopssearch><span>服务查询和详情展示</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopsapidocs-li><input type=checkbox id=m-zh-cndocsv27adminopsapidocs-check>
<label for=m-zh-cndocsv27adminopsapidocs-check><a href=/zh-cn/docsv2.7/admin/ops/apidocs/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopsapidocs><span>API文档&测试</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopsgovernance-li><input type=checkbox id=m-zh-cndocsv27adminopsgovernance-check>
<label for=m-zh-cndocsv27adminopsgovernance-check><a href=/zh-cn/docsv2.7/admin/ops/governance/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopsgovernance><span>服务治理和配置管理</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopsskywalking-li><input type=checkbox id=m-zh-cndocsv27adminopsskywalking-check>
<label for=m-zh-cndocsv27adminopsskywalking-check><a href=/zh-cn/docsv2.7/admin/ops/skywalking/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopsskywalking><span>使用 Apache Skywalking 做分布式跟踪</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27adminopspinpoint-li><input type=checkbox id=m-zh-cndocsv27adminopspinpoint-check>
<label for=m-zh-cndocsv27adminopspinpoint-check><a href=/zh-cn/docsv2.7/admin/ops/pinpoint/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27adminopspinpoint><span>使用 Pinpoint 做分布式跟踪</span></a></label></li></ul></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section with-child" id=m-zh-cndocsv27admininstall-li><input type=checkbox id=m-zh-cndocsv27admininstall-check>
<label for=m-zh-cndocsv27admininstall-check><a href=/zh-cn/docsv2.7/admin/install/ class="align-left pl-0 td-sidebar-link td-sidebar-link__section" id=m-zh-cndocsv27admininstall><span>安装手册</span></a></label><ul class="ul-3 foldable"><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstalladmin-console-li><input type=checkbox id=m-zh-cndocsv27admininstalladmin-console-check>
<label for=m-zh-cndocsv27admininstalladmin-console-check><a href=/zh-cn/docsv2.7/admin/install/admin-console/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstalladmin-console><span>管理控制台安装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstallprovider-demo-li><input type=checkbox id=m-zh-cndocsv27admininstallprovider-demo-check>
<label for=m-zh-cndocsv27admininstallprovider-demo-check><a href=/zh-cn/docsv2.7/admin/install/provider-demo/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstallprovider-demo><span>示例提供者安装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstallconsumer-demo-li><input type=checkbox id=m-zh-cndocsv27admininstallconsumer-demo-check>
<label for=m-zh-cndocsv27admininstallconsumer-demo-check><a href=/zh-cn/docsv2.7/admin/install/consumer-demo/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstallconsumer-demo><span>示例消费者安装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstallredis-li><input type=checkbox id=m-zh-cndocsv27admininstallredis-check>
<label for=m-zh-cndocsv27admininstallredis-check><a href=/zh-cn/docsv2.7/admin/install/redis/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstallredis><span>Redis 注册中心安装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstallzookeeper-li><input type=checkbox id=m-zh-cndocsv27admininstallzookeeper-check>
<label for=m-zh-cndocsv27admininstallzookeeper-check><a href=/zh-cn/docsv2.7/admin/install/zookeeper/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstallzookeeper><span>Zookeeper 注册中心安装</span></a></label></li><li class="td-sidebar-nav__section-title td-sidebar-nav__section without-child" id=m-zh-cndocsv27admininstallmonitor-center-li><input type=checkbox id=m-zh-cndocsv27admininstallmonitor-center-check>
<label for=m-zh-cndocsv27admininstallmonitor-center-check><a href=/zh-cn/docsv2.7/admin/install/monitor-center/ class="align-left pl-0 td-sidebar-link td-sidebar-link__page" id=m-zh-cndocsv27admininstallmonitor-center><span>Simple 监控中心安装</span></a></label></li></ul></li></ul></li></ul></li></ul></nav></div></div><main class="col-12 col-md-9 col-xl-8 pl-md-5" role=main><nav aria-label=breadcrumb class=td-breadcrumbs><ol class=breadcrumb><li class=breadcrumb-item><a href=https://cn.dubbo.apache.org/zh-cn/docsv2.7/>文档 2.x</a></li><li class=breadcrumb-item><a href=https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/>开发指南</a></li><li class=breadcrumb-item><a href=https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/>源代码</a></li><li class="breadcrumb-item active" aria-current=page><a href=https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/ aria-disabled=true class="btn-link disabled">服务导出</a></li></ol></nav><div class=td-content><h1>服务导出</h1><div class=lead>本文介绍了 Dubbo 服务导出的过程和实现细节</div><header class=article-meta></header><h2 id=1简介>1.简介</h2><p>本篇文章,我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。本篇文章将会对这三个部分代码进行详细的分析。</p><h2 id=2源码分析>2.源码分析</h2><p>服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。方法代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>onApplicationEvent</span>(ContextRefreshedEvent event) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 是否有延迟导出 &amp;&amp; 是否已导出 &amp;&amp; 是不是已被取消导出</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (isDelay() <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>isExported() <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>isUnexported()) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 导出服务</span>
</span></span><span style=display:flex><span> export();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>这个方法首先会根据条件决定是否导出服务,比如有些服务设置了延时导出,那么此时就不应该在此处导出。还有一些服务已经被导出了,或者当前服务被取消导出了,此时也不能再次导出相关服务。注意这里的 isDelay 方法,这个方法字面意思是“是否延迟导出服务”,返回 true 表示延迟导出,false 表示不延迟导出。但是该方法真实意思却并非如此,当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。与字面意思恰恰相反,这个需要大家注意一下。下面我们来看一下这个方法的逻辑。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#586e75>// -☆- ServiceBean</span>
</span></span><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>boolean</span> <span style=color:#268bd2>isDelay</span>() {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 delay</span>
</span></span><span style=display:flex><span> Integer delay <span style=color:#719e07>=</span> getDelay();
</span></span><span style=display:flex><span> ProviderConfig provider <span style=color:#719e07>=</span> getProvider();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (delay <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> provider <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果前面获取的 delay 为空,这里继续获取</span>
</span></span><span style=display:flex><span> delay <span style=color:#719e07>=</span> provider.getDelay();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 判断 delay 是否为空,或者等于 -1</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> supportedApplicationListener <span style=color:#719e07>&amp;&amp;</span> (delay <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> delay <span style=color:#719e07>==</span> <span style=color:#719e07>-</span>1);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>暂时忽略 supportedApplicationListener 这个条件,当 delay 为空,或者等于-1时,该方法返回 true,而不是 false。这个方法的返回值让人有点困惑。该方法目前已被重构,详细请参考 <a href=https://github.com/apache/dubbo/pull/2686>dubbo #2686</a></p><p>现在解释一下 supportedApplicationListener 变量含义,该变量用于表示当前的 Spring 容器是否支持 ApplicationListener,这个值初始为 false。在 Spring 容器将自己设置到 ServiceBean 中时,ServiceBean 的 setApplicationContext 方法会检测 Spring 容器是否支持 ApplicationListener。若支持,则将 supportedApplicationListener 置为 true。ServiceBean 是 Dubbo 与 Spring 框架进行整合的关键,可以看做是两个框架之间的桥梁。具有同样作用的类还有 ReferenceBean。</p><p>现在我们知道了 Dubbo 服务导出过程的起点,接下来对服务导出的前置逻辑进行分析。</p><h3 id=21-前置工作>2.1 前置工作</h3><p>前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。这一点,官方文档中有所说明。</p><blockquote><p>采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。</p></blockquote><p>接下来,我们先来分析配置检查部分的源码,随后再来分析 URL 组装部分的源码。</p><h4 id=211-检查配置>2.1.1 检查配置</h4><p>本节我们接着前面的源码向下分析,前面说过 onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。那么下面我们从 export 方法开始进行分析,如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>synchronized</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>export</span>() {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (provider <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 export 和 delay 配置</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (export <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> export <span style=color:#719e07>=</span> provider.getExport();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (delay <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> delay <span style=color:#719e07>=</span> provider.getDelay();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果 export 为 false,则不导出服务</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (export <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>export) {
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// delay &gt; 0,延时导出服务</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (delay <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> delay <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> delayExportExecutor.schedule(<span style=color:#719e07>new</span> Runnable() {
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>run</span>() {
</span></span><span style=display:flex><span> doExport();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }, delay, TimeUnit.MILLISECONDS);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 立即导出服务</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> doExport();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>export 方法对两项配置进行了检查,并根据配置执行相应的动作。首先是 export 配置,这个配置决定了是否导出服务。有时候我们只是想本地启动服务进行一些调试工作,我们并不希望把本地启动的服务暴露出去给别人调用。此时,我们可通过配置 export 禁止服务导出,比如:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-xml data-lang=xml><span style=display:flex><span><span style=color:#268bd2>&lt;dubbo:provider</span> export=<span style=color:#2aa198>&#34;false&#34;</span> <span style=color:#268bd2>/&gt;</span>
</span></span></code></pre></div><p>delay 配置顾名思义,用于延迟导出服务,这个就不分析了。下面,我们继续分析源码,这次要分析的是 doExport 方法。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>protected</span> <span style=color:#268bd2>synchronized</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doExport</span>() {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (unexported) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;Already unexported!&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (exported) {
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> exported <span style=color:#719e07>=</span> <span style=color:#cb4b16>true</span>;
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 interfaceName 是否合法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (interfaceName <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> interfaceName.length() <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;interface not allow null!&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化</span>
</span></span><span style=display:flex><span> checkDefault();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 若为空,则尝试从其他配置类对象中获取相应的实例。</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (provider <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (application <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> application <span style=color:#719e07>=</span> provider.getApplication();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (module <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> module <span style=color:#719e07>=</span> provider.getModule();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registries <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {...}
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (monitor <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {...}
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (protocols <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {...}
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (module <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registries <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> registries <span style=color:#719e07>=</span> module.getRegistries();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (monitor <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {...}
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (application <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registries <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> registries <span style=color:#719e07>=</span> application.getRegistries();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (monitor <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {...}
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 ref 是否为泛化服务类型</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ref <span style=color:#719e07>instanceof</span> GenericService) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置 interfaceClass 为 GenericService.class</span>
</span></span><span style=display:flex><span> interfaceClass <span style=color:#719e07>=</span> GenericService.class;
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (StringUtils.isEmpty(generic)) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置 generic = &#34;true&#34;</span>
</span></span><span style=display:flex><span> generic <span style=color:#719e07>=</span> Boolean.TRUE.toString();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// ref 非 GenericService 类型</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> interfaceClass <span style=color:#719e07>=</span> Class.forName(interfaceName, <span style=color:#cb4b16>true</span>, Thread.currentThread()
</span></span><span style=display:flex><span> .getContextClassLoader());
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (ClassNotFoundException e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(e.getMessage(), e);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 对 interfaceClass,以及 &lt;dubbo:method&gt; 标签中的必要字段进行检查</span>
</span></span><span style=display:flex><span> checkInterfaceAndMethods(interfaceClass, methods);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 对 ref 合法性进行检测</span>
</span></span><span style=display:flex><span> checkRef();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置 generic = &#34;false&#34;</span>
</span></span><span style=display:flex><span> generic <span style=color:#719e07>=</span> Boolean.FALSE.toString();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// local 和 stub 在功能应该是一致的,用于配置本地存根</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (local <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#2aa198>&#34;true&#34;</span>.equals(local)) {
</span></span><span style=display:flex><span> local <span style=color:#719e07>=</span> interfaceName <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;Local&#34;</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;</span> localClass;
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取本地存根类</span>
</span></span><span style=display:flex><span> localClass <span style=color:#719e07>=</span> ClassHelper.forNameWithThreadContextClassLoader(local);
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (ClassNotFoundException e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(e.getMessage(), e);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>interfaceClass.isAssignableFrom(localClass)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;The local implementation class &#34;</span> <span style=color:#719e07>+</span> localClass.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34; not implement interface &#34;</span> <span style=color:#719e07>+</span> interfaceName);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (stub <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 此处的代码和上一个 if 分支的代码基本一致,这里省略</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测各种对象是否为空,为空则新建,或者抛出异常</span>
</span></span><span style=display:flex><span> checkApplication();
</span></span><span style=display:flex><span> checkRegistry();
</span></span><span style=display:flex><span> checkProtocol();
</span></span><span style=display:flex><span> appendProperties(<span style=color:#719e07>this</span>);
</span></span><span style=display:flex><span> checkStubAndMock(interfaceClass);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (path <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> path.length() <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> path <span style=color:#719e07>=</span> interfaceName;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 导出服务</span>
</span></span><span style=display:flex><span> doExportUrls();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// ApplicationModel 持有所有的 ProviderModel。</span>
</span></span><span style=display:flex><span> ProviderModel providerModel <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ProviderModel(getUniqueServiceName(), <span style=color:#719e07>this</span>, ref);
</span></span><span style=display:flex><span> ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>以上就是配置检查的相关分析,代码比较多,需要大家耐心看一下。下面对配置检查的逻辑进行简单的总结,如下:</p><ol><li>检测 &lt;dubbo:service> 标签的 interface 属性合法性,不合法则抛出异常</li><li>检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。</li><li>检测并处理泛化服务和普通服务类</li><li>检测本地存根配置,并进行相应的处理</li><li>对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常</li></ol><p>配置检查并非本文重点,因此这里不打算对 doExport 方法所调用的方法进行分析(doExportUrls 方法除外)。在这些方法中,除了 appendProperties 方法稍微复杂一些,其他方法逻辑不是很复杂。因此,大家可自行分析。</p><h4 id=212-多协议多注册中心导出服务>2.1.2 多协议多注册中心导出服务</h4><p>Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。相关代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doExportUrls</span>() {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 加载注册中心链接</span>
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> registryURLs <span style=color:#719e07>=</span> loadRegistries(<span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 遍历 protocols,并在每个协议下导出服务</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (ProtocolConfig protocolConfig : protocols) {
</span></span><span style=display:flex><span> doExportUrlsFor1Protocol(protocolConfig, registryURLs);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码首先是通过 loadRegistries 加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心。下面,我们先来看一下 loadRegistries 方法的逻辑。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>protected</span> List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>loadRegistries</span>(<span style=color:#dc322f>boolean</span> provider) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测是否存在注册中心配置类,不存在则抛出异常</span>
</span></span><span style=display:flex><span> checkRegistry();
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> registryList <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ArrayList<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registries <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>registries.isEmpty()) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (RegistryConfig config : registries) {
</span></span><span style=display:flex><span> String address <span style=color:#719e07>=</span> config.getAddress();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (address <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> address.length() <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 若 address 为空,则将其设为 0.0.0.0</span>
</span></span><span style=display:flex><span> address <span style=color:#719e07>=</span> Constants.ANYHOST_VALUE;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 从系统属性中加载注册中心地址</span>
</span></span><span style=display:flex><span> String sysaddress <span style=color:#719e07>=</span> System.getProperty(<span style=color:#2aa198>&#34;dubbo.registry.address&#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (sysaddress <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> sysaddress.length() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> address <span style=color:#719e07>=</span> sysaddress;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 address 是否合法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (address.length() <span style=color:#719e07>&gt;</span> 0 <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
</span></span><span style=display:flex><span> Map<span style=color:#719e07>&lt;</span>String, String<span style=color:#719e07>&gt;</span> map <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> HashMap<span style=color:#719e07>&lt;</span>String, String<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 ApplicationConfig 中的字段信息到 map 中</span>
</span></span><span style=display:flex><span> appendParameters(map, application);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 RegistryConfig 字段信息到 map 中</span>
</span></span><span style=display:flex><span> appendParameters(map, config);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 path、pid,protocol 等信息到 map 中</span>
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;path&#34;</span>, RegistryService.class.getName());
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;dubbo&#34;</span>, Version.getProtocolVersion());
</span></span><span style=display:flex><span> map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ConfigUtils.getPid() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>map.containsKey(<span style=color:#2aa198>&#34;protocol&#34;</span>)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension(<span style=color:#2aa198>&#34;remote&#34;</span>)) {
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;protocol&#34;</span>, <span style=color:#2aa198>&#34;remote&#34;</span>);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;protocol&#34;</span>, <span style=color:#2aa198>&#34;dubbo&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 解析得到 URL 列表,address 可能包含多个注册中心 ip,</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 因此解析得到的是一个 URL 列表</span>
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> urls <span style=color:#719e07>=</span> UrlUtils.parseURLs(address, map);
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (URL url : urls) {
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
</span></span><span style=display:flex><span> <span style=color:#586e75>// 将 URL 协议头设置为 registry</span>
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.setProtocol(Constants.REGISTRY_PROTOCOL);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// (服务提供者 &amp;&amp; register = true 或 null) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// || (非服务提供者 &amp;&amp; subscribe = true 或 null)</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> ((provider <span style=color:#719e07>&amp;&amp;</span> url.getParameter(Constants.REGISTER_KEY, <span style=color:#cb4b16>true</span>))
</span></span><span style=display:flex><span> <span style=color:#719e07>||</span> (<span style=color:#719e07>!</span>provider <span style=color:#719e07>&amp;&amp;</span> url.getParameter(Constants.SUBSCRIBE_KEY, <span style=color:#cb4b16>true</span>))) {
</span></span><span style=display:flex><span> registryList.add(url);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> registryList;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>loadRegistries 方法主要包含如下的逻辑:</p><ol><li>检测是否存在注册中心配置类,不存在则抛出异常</li><li>构建参数映射集合,也就是 map</li><li>构建注册中心链接列表</li><li>遍历链接列表,并根据条件决定是否将其添加到 registryList 中</li></ol><p>关于多协议多注册中心导出服务就先分析到这,代码不是很多,接下来分析 URL 组装过程。</p><h4 id=213-组装-url>2.1.3 组装 URL</h4><p>配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo,犹如水之于鱼,非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。既然 URL 如此重要,那么下面我们来了解一下 URL 组装的过程。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> registryURLs) {
</span></span><span style=display:flex><span> String name <span style=color:#719e07>=</span> protocolConfig.getName();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果协议名为空,或空串,则将协议名变量设置为 dubbo</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (name <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> name.length() <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> name <span style=color:#719e07>=</span> <span style=color:#2aa198>&#34;dubbo&#34;</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> Map<span style=color:#719e07>&lt;</span>String, String<span style=color:#719e07>&gt;</span> map <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> HashMap<span style=color:#719e07>&lt;</span>String, String<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 side、版本、时间戳以及进程号等信息到 map 中</span>
</span></span><span style=display:flex><span> map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
</span></span><span style=display:flex><span> map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
</span></span><span style=display:flex><span> map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ConfigUtils.getPid() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 通过反射将对象的字段信息添加到 map 中</span>
</span></span><span style=display:flex><span> appendParameters(map, application);
</span></span><span style=display:flex><span> appendParameters(map, module);
</span></span><span style=display:flex><span> appendParameters(map, provider, Constants.DEFAULT_KEY);
</span></span><span style=display:flex><span> appendParameters(map, protocolConfig);
</span></span><span style=display:flex><span> appendParameters(map, <span style=color:#719e07>this</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// methods 为 MethodConfig 集合,MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (methods <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>methods.isEmpty()) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 这段代码用于添加 Callback 配置到 map 中,代码太长,待会单独分析</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 generic 是否为 &#34;true&#34;,并根据检测结果向 map 中添加不同的信息</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ProtocolUtils.isGeneric(generic)) {
</span></span><span style=display:flex><span> map.put(Constants.GENERIC_KEY, generic);
</span></span><span style=display:flex><span> map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> String revision <span style=color:#719e07>=</span> Version.getVersion(interfaceClass, version);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (revision <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> revision.length() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;revision&#34;</span>, revision);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等</span>
</span></span><span style=display:flex><span> String<span style=color:#719e07>[]</span> methods <span style=color:#719e07>=</span> Wrapper.getWrapper(interfaceClass).getMethodNames();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (methods.length <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> logger.warn(<span style=color:#2aa198>&#34;NO method found in service interface ...&#34;</span>);
</span></span><span style=display:flex><span> map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中</span>
</span></span><span style=display:flex><span> map.put(Constants.METHODS_KEY, StringUtils.join(<span style=color:#719e07>new</span> HashSet<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span>(Arrays.asList(methods)), <span style=color:#2aa198>&#34;,&#34;</span>));
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 token 到 map 中</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>ConfigUtils.isEmpty(token)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ConfigUtils.isDefault(token)) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 随机生成 token</span>
</span></span><span style=display:flex><span> map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> map.put(Constants.TOKEN_KEY, token);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 判断协议名是否为 injvm</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
</span></span><span style=display:flex><span> protocolConfig.setRegister(<span style=color:#cb4b16>false</span>);
</span></span><span style=display:flex><span> map.put(<span style=color:#2aa198>&#34;notify&#34;</span>, <span style=color:#2aa198>&#34;false&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取上下文路径</span>
</span></span><span style=display:flex><span> String contextPath <span style=color:#719e07>=</span> protocolConfig.getContextpath();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> ((contextPath <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> contextPath.length() <span style=color:#719e07>==</span> 0) <span style=color:#719e07>&amp;&amp;</span> provider <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> contextPath <span style=color:#719e07>=</span> provider.getContextpath();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 host 和 port</span>
</span></span><span style=display:flex><span> String host <span style=color:#719e07>=</span> <span style=color:#719e07>this</span>.findConfigedHosts(protocolConfig, registryURLs, map);
</span></span><span style=display:flex><span> Integer port <span style=color:#719e07>=</span> <span style=color:#719e07>this</span>.findConfigedPorts(protocolConfig, name, map);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 组装 URL</span>
</span></span><span style=display:flex><span> URL url <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> URL(name, host, port, (contextPath <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> contextPath.length() <span style=color:#719e07>==</span> 0 <span style=color:#719e07>?</span> <span style=color:#2aa198>&#34;&#34;</span> : contextPath <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;/&#34;</span>) <span style=color:#719e07>+</span> path, map);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 省略无关代码</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是,这里出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。</p><p>上面省略了一段代码,这里简单分析一下。这段代码用于检测 &lt;dubbo:method> 标签中的配置信息,并将相关配置添加到 map 中。代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> registryURLs) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// ...</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// methods 为 MethodConfig 集合,MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (methods <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>methods.isEmpty()) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (MethodConfig method : methods) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 比如存储 &lt;dubbo:method name=&#34;sayHello&#34; retries=&#34;2&#34;&gt; 对应的 MethodConfig,</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 键 = sayHello.retries,map = {&#34;sayHello.retries&#34;: 2, &#34;xxx&#34;: &#34;yyy&#34;}</span>
</span></span><span style=display:flex><span> appendParameters(map, method, method.getName());
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> String retryKey <span style=color:#719e07>=</span> method.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.retry&#34;</span>;
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (map.containsKey(retryKey)) {
</span></span><span style=display:flex><span> String retryValue <span style=color:#719e07>=</span> map.remove(retryKey);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#2aa198>&#34;false&#34;</span>.equals(retryValue)) {
</span></span><span style=display:flex><span> map.put(method.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.retries&#34;</span>, <span style=color:#2aa198>&#34;0&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 ArgumentConfig 列表</span>
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>ArgumentConfig<span style=color:#719e07>&gt;</span> arguments <span style=color:#719e07>=</span> method.getArguments();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (arguments <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>arguments.isEmpty()) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (ArgumentConfig argument : arguments) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 type 属性是否为空,或者空串(分支1 ⭐️)</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (argument.getType() <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> argument.getType().length() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> Method<span style=color:#719e07>[]</span> methods <span style=color:#719e07>=</span> interfaceClass.getMethods();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (methods <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> methods.length <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (<span style=color:#dc322f>int</span> i <span style=color:#719e07>=</span> 0; i <span style=color:#719e07>&lt;</span> methods.length; i<span style=color:#719e07>++</span>) {
</span></span><span style=display:flex><span> String methodName <span style=color:#719e07>=</span> methods<span style=color:#719e07>[</span>i<span style=color:#719e07>]</span>.getName();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 比对方法名,查找目标方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (methodName.equals(method.getName())) {
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;[]</span> argtypes <span style=color:#719e07>=</span> methods<span style=color:#719e07>[</span>i<span style=color:#719e07>]</span>.getParameterTypes();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (argument.getIndex() <span style=color:#719e07>!=</span> <span style=color:#719e07>-</span>1) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 ArgumentConfig 中的 type 属性与方法参数列表</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 中的参数名称是否一致,不一致则抛出异常(分支2 ⭐️)</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (argtypes<span style=color:#719e07>[</span>argument.getIndex()<span style=color:#719e07>]</span>.getName().equals(argument.getType())) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 ArgumentConfig 字段信息到 map 中,</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 键前缀 = 方法名.index,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// map = {&#34;sayHello.3&#34;: true}</span>
</span></span><span style=display:flex><span> appendParameters(map, argument, method.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.&#34;</span> <span style=color:#719e07>+</span> argument.getIndex());
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;argument config error: ...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> { <span style=color:#586e75>// 分支3 ⭐️</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (<span style=color:#dc322f>int</span> j <span style=color:#719e07>=</span> 0; j <span style=color:#719e07>&lt;</span> argtypes.length; j<span style=color:#719e07>++</span>) {
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;</span> argclazz <span style=color:#719e07>=</span> argtypes<span style=color:#719e07>[</span>j<span style=color:#719e07>]</span>;
</span></span><span style=display:flex><span> <span style=color:#586e75>// 从参数类型列表中查找类型名称为 argument.type 的参数</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (argclazz.getName().equals(argument.getType())) {
</span></span><span style=display:flex><span> appendParameters(map, argument, method.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.&#34;</span> <span style=color:#719e07>+</span> j);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (argument.getIndex() <span style=color:#719e07>!=</span> <span style=color:#719e07>-</span>1 <span style=color:#719e07>&amp;&amp;</span> argument.getIndex() <span style=color:#719e07>!=</span> j) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;argument config error: ...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 用户未配置 type 属性,但配置了 index 属性,且 index != -1</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> (argument.getIndex() <span style=color:#719e07>!=</span> <span style=color:#719e07>-</span>1) { <span style=color:#586e75>// 分支4 ⭐️</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 ArgumentConfig 字段信息到 map 中</span>
</span></span><span style=display:flex><span> appendParameters(map, argument, method.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.&#34;</span> <span style=color:#719e07>+</span> argument.getIndex());
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;argument config must set index or type&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// ...</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面这段代码 for 循环和 if else 分支嵌套太多,导致层次太深,不利于阅读,需要耐心看一下。大家在看这段代码时,注意把几个重要的条件分支找出来。只要理解了这几个分支的意图,就可以弄懂这段代码。请注意上面代码中⭐️符号,这几个符号标识出了4个重要的分支,下面用伪代码解释一下这几个分支的含义。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#586e75>// 获取 ArgumentConfig 列表</span>
</span></span><span style=display:flex><span><span style=color:#719e07>for</span> (遍历 ArgumentConfig 列表) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (type 不为 <span style=color:#cb4b16>null</span>,也不为空串) { <span style=color:#586e75>// 分支1</span>
</span></span><span style=display:flex><span> 1. 通过反射获取 interfaceClass 的方法列表
</span></span><span style=display:flex><span> <span style=color:#268bd2>for</span> (遍历方法列表) {
</span></span><span style=display:flex><span> 1. 比对方法名,查找目标方法
</span></span><span style=display:flex><span> 2. 通过反射获取目标方法的参数类型数组 argtypes
</span></span><span style=display:flex><span> <span style=color:#268bd2>if</span> (index <span style=color:#719e07>!=</span> <span style=color:#719e07>-</span>1) { <span style=color:#586e75>// 分支2</span>
</span></span><span style=display:flex><span> 1. 从 argtypes 数组中获取下标 index 处的元素 argType
</span></span><span style=display:flex><span> 2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
</span></span><span style=display:flex><span> 3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> { <span style=color:#586e75>// 分支3</span>
</span></span><span style=display:flex><span> 1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
</span></span><span style=display:flex><span> 2. 添加 ArgumentConfig 字段信息到 map 中
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> (index <span style=color:#719e07>!=</span> <span style=color:#719e07>-</span>1) { <span style=color:#586e75>// 分支4</span>
</span></span><span style=display:flex><span> 1. 添加 ArgumentConfig 字段信息到 map 中
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>在本节分析的源码中,appendParameters 这个方法出现的次数比较多,该方法用于将对象字段信息添加到 map 中。实现上则是通过反射获取目标对象的 getter 方法,并调用该方法获取属性值。然后再通过 getter 方法名解析出属性名,比如从方法名 getName 中可解析出属性 name。如果用户传入了属性名前缀,此时需要将属性名加入前缀内容。最后将 &lt;属性名,属性值> 键值对存入到 map 中就行了。限于篇幅原因,这里就不分析 appendParameters 方法的源码了,大家请自行分析。</p><h3 id=22-导出-dubbo-服务>2.2 导出 Dubbo 服务</h3><p>前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style=color:#719e07>&lt;</span>URL<span style=color:#719e07>&gt;</span> registryURLs) {
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 省略无关代码</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
</span></span><span style=display:flex><span> .hasExtension(url.getProtocol())) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url</span>
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
</span></span><span style=display:flex><span> .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> String scope <span style=color:#719e07>=</span> url.getParameter(Constants.SCOPE_KEY);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果 scope = none,则什么都不做</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// scope != remote,导出到本地</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
</span></span><span style=display:flex><span> exportLocal(url);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// scope != local,导出到远程</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registryURLs <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>registryURLs.isEmpty()) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (URL registryURL : registryURLs) {
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 加载监视器链接</span>
</span></span><span style=display:flex><span> URL monitorUrl <span style=color:#719e07>=</span> loadMonitor(registryURL);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (monitorUrl <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 将监视器链接作为参数添加到 url 中</span>
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> String proxy <span style=color:#719e07>=</span> url.getParameter(Constants.PROXY_KEY);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (StringUtils.isNotEmpty(proxy)) {
</span></span><span style=display:flex><span> registryURL <span style=color:#719e07>=</span> registryURL.addParameter(Constants.PROXY_KEY, proxy);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 为服务提供类(ref)生成 Invoker</span>
</span></span><span style=display:flex><span> Invoker<span style=color:#719e07>&lt;?&gt;</span> invoker <span style=color:#719e07>=</span> proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
</span></span><span style=display:flex><span> <span style=color:#586e75>// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig</span>
</span></span><span style=display:flex><span> DelegateProviderMetaDataInvoker wrapperInvoker <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> DelegateProviderMetaDataInvoker(invoker, <span style=color:#719e07>this</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 导出服务,并生成 Exporter</span>
</span></span><span style=display:flex><span> Exporter<span style=color:#719e07>&lt;?&gt;</span> exporter <span style=color:#719e07>=</span> protocol.export(wrapperInvoker);
</span></span><span style=display:flex><span> exporters.add(exporter);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 不存在注册中心,仅导出服务</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> Invoker<span style=color:#719e07>&lt;?&gt;</span> invoker <span style=color:#719e07>=</span> proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
</span></span><span style=display:flex><span> DelegateProviderMetaDataInvoker wrapperInvoker <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> DelegateProviderMetaDataInvoker(invoker, <span style=color:#719e07>this</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> Exporter<span style=color:#719e07>&lt;?&gt;</span> exporter <span style=color:#719e07>=</span> protocol.export(wrapperInvoker);
</span></span><span style=display:flex><span> exporters.add(exporter);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>this</span>.urls.add(url);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:</p><ul><li>scope = none,不导出服务</li><li>scope != remote,导出到本地</li><li>scope != local,导出到远程</li></ul><p>不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。</p><h3 id=221-invoker-创建过程>2.2.1 Invoker 创建过程</h3><p>在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。</p><blockquote><p>Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。</p></blockquote><p>既然 Invoker 如此重要,那么我们很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中,探索 Invoker 的创建过程。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>getInvoker</span>(T proxy, Class<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> type, URL url) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 为目标类创建 Wrapper</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> Wrapper wrapper <span style=color:#719e07>=</span> Wrapper.getWrapper(proxy.getClass().getName().indexOf(<span style=color:#2aa198>&#39;$&#39;</span>) <span style=color:#719e07>&lt;</span> 0 <span style=color:#719e07>?</span> proxy.getClass() : type);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建匿名 Invoker 类对象,并实现 doInvoke 方法。</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> AbstractProxyInvoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>(proxy, type, url) {
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>protected</span> Object <span style=color:#268bd2>doInvoke</span>(T proxy, String methodName,
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;[]</span> parameterTypes,
</span></span><span style=display:flex><span> Object<span style=color:#719e07>[]</span> arguments) <span style=color:#268bd2>throws</span> Throwable {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> };
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。覆写后的 doInvoke 逻辑比较简单,仅是将调用请求转发给了 Wrapper 类的 invokeMethod 方法。Wrapper 用于“包裹”目标类,Wrapper 是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中,子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。相关的代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#268bd2>static</span> Wrapper <span style=color:#268bd2>getWrapper</span>(Class<span style=color:#719e07>&lt;?&gt;</span> c) {
</span></span><span style=display:flex><span> <span style=color:#719e07>while</span> (ClassGenerator.isDynamicClass(c))
</span></span><span style=display:flex><span> c <span style=color:#719e07>=</span> c.getSuperclass();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (c <span style=color:#719e07>==</span> Object.class)
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> OBJECT_WRAPPER;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 从缓存中获取 Wrapper 实例</span>
</span></span><span style=display:flex><span> Wrapper ret <span style=color:#719e07>=</span> WRAPPER_MAP.get(c);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ret <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 缓存未命中,创建 Wrapper</span>
</span></span><span style=display:flex><span> ret <span style=color:#719e07>=</span> makeWrapper(c);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 写入缓存</span>
</span></span><span style=display:flex><span> WRAPPER_MAP.put(c, ret);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> ret;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>getWrapper 方法仅包含一些缓存操作逻辑,不难理解。下面我们看一下 makeWrapper 方法。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#268bd2>static</span> Wrapper <span style=color:#268bd2>makeWrapper</span>(Class<span style=color:#719e07>&lt;?&gt;</span> c) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 c 是否为基本类型,若是则抛出异常</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (c.isPrimitive())
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;Can not create wrapper for primitive type: &#34;</span> <span style=color:#719e07>+</span> c);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> String name <span style=color:#719e07>=</span> c.getName();
</span></span><span style=display:flex><span> ClassLoader cl <span style=color:#719e07>=</span> ClassHelper.getClassLoader(c);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// c1 用于存储 setPropertyValue 方法代码</span>
</span></span><span style=display:flex><span> StringBuilder c1 <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> StringBuilder(<span style=color:#2aa198>&#34;public void setPropertyValue(Object o, String n, Object v){ &#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#586e75>// c2 用于存储 getPropertyValue 方法代码</span>
</span></span><span style=display:flex><span> StringBuilder c2 <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> StringBuilder(<span style=color:#2aa198>&#34;public Object getPropertyValue(Object o, String n){ &#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#586e75>// c3 用于存储 invokeMethod 方法代码</span>
</span></span><span style=display:flex><span> StringBuilder c3 <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> StringBuilder(<span style=color:#2aa198>&#34;public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws &#34;</span> <span style=color:#719e07>+</span> InvocationTargetException.class.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;{ &#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成类型转换代码及异常捕捉代码,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }</span>
</span></span><span style=display:flex><span> c1.append(name).append(<span style=color:#2aa198>&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style=color:#2aa198>&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>);
</span></span><span style=display:flex><span> c2.append(name).append(<span style=color:#2aa198>&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style=color:#2aa198>&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>);
</span></span><span style=display:flex><span> c3.append(name).append(<span style=color:#2aa198>&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style=color:#2aa198>&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// pts 用于存储成员变量名和类型</span>
</span></span><span style=display:flex><span> Map<span style=color:#719e07>&lt;</span>String, Class<span style=color:#719e07>&lt;?&gt;&gt;</span> pts <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> HashMap<span style=color:#719e07>&lt;</span>String, Class<span style=color:#719e07>&lt;?&gt;&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#586e75>// ms 用于存储方法描述信息(可理解为方法签名)及 Method 实例</span>
</span></span><span style=display:flex><span> Map<span style=color:#719e07>&lt;</span>String, Method<span style=color:#719e07>&gt;</span> ms <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> LinkedHashMap<span style=color:#719e07>&lt;</span>String, Method<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#586e75>// mns 为方法名列表</span>
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span> mns <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ArrayList<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span> <span style=color:#586e75>// dmns 用于存储“定义在当前类中的方法”的名称</span>
</span></span><span style=display:flex><span> List<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span> dmns <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ArrayList<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span>();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// --------------------------------✨ 分割线1 ✨-------------------------------------</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 public 访问级别的字段,并为所有字段生成条件判断语句</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (Field f : c.getFields()) {
</span></span><span style=display:flex><span> String fn <span style=color:#719e07>=</span> f.getName();
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;</span> ft <span style=color:#719e07>=</span> f.getType();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (Modifier.isStatic(f.getModifiers()) <span style=color:#719e07>||</span> Modifier.isTransient(f.getModifiers()))
</span></span><span style=display:flex><span> <span style=color:#586e75>// 忽略关键字 static 或 transient 修饰的变量</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>continue</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成条件判断及赋值语句,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;name&#34;) ) { w.name = (java.lang.String) $3; return;}</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;age&#34;) ) { w.age = ((Number) $3).intValue(); return;}</span>
</span></span><span style=display:flex><span> c1.append(<span style=color:#2aa198>&#34; if( $2.equals(\&#34;&#34;</span>).append(fn).append(<span style=color:#2aa198>&#34;\&#34;) ){ w.&#34;</span>).append(fn).append(<span style=color:#2aa198>&#34;=&#34;</span>).append(arg(ft, <span style=color:#2aa198>&#34;$3&#34;</span>)).append(<span style=color:#2aa198>&#34;; return; }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成条件判断及返回语句,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;name&#34;) ) { return ($w)w.name; }</span>
</span></span><span style=display:flex><span> c2.append(<span style=color:#2aa198>&#34; if( $2.equals(\&#34;&#34;</span>).append(fn).append(<span style=color:#2aa198>&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(fn).append(<span style=color:#2aa198>&#34;; }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 存储 &lt;字段名, 字段类型&gt; 键值对到 pts 中</span>
</span></span><span style=display:flex><span> pts.put(fn, ft);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// --------------------------------✨ 分割线2 ✨-------------------------------------</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> Method<span style=color:#719e07>[]</span> methods <span style=color:#719e07>=</span> c.getMethods();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测 c 中是否包含在当前类中声明的方法</span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> hasMethod <span style=color:#719e07>=</span> hasMethods(methods);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (hasMethod) {
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; try{&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (Method m : methods) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (m.getDeclaringClass() <span style=color:#719e07>==</span> Object.class)
</span></span><span style=display:flex><span> <span style=color:#586e75>// 忽略 Object 中定义的方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>continue</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> String mn <span style=color:#719e07>=</span> m.getName();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成方法名判断语句,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if ( &#34;sayHello&#34;.equals( $2 )</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; if( \&#34;&#34;</span>).append(mn).append(<span style=color:#2aa198>&#34;\&#34;.equals( $2 ) &#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#dc322f>int</span> len <span style=color:#719e07>=</span> m.getParameterTypes().length;
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成“运行时传入的参数数量与方法参数列表长度”判断语句,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3.length == 2</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; &amp;&amp; &#34;</span>).append(<span style=color:#2aa198>&#34; $3.length == &#34;</span>).append(len);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> override <span style=color:#719e07>=</span> <span style=color:#cb4b16>false</span>;
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (Method m2 : methods) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测方法是否存在重载情况,条件为:方法对象不同 &amp;&amp; 方法名相同</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (m <span style=color:#719e07>!=</span> m2 <span style=color:#719e07>&amp;&amp;</span> m.getName().equals(m2.getName())) {
</span></span><span style=display:flex><span> override <span style=color:#719e07>=</span> <span style=color:#cb4b16>true</span>;
</span></span><span style=display:flex><span> <span style=color:#719e07>break</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 对重载方法进行处理,考虑下面的方法:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 1. void sayHello(Integer, String)</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 2. void sayHello(Integer, Integer)</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 方法名相同,参数列表长度也相同,因此不能仅通过这两项判断两个方法是否相等。</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 需要进一步判断方法的参数类型</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (override) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (len <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (<span style=color:#dc322f>int</span> l <span style=color:#719e07>=</span> 0; l <span style=color:#719e07>&lt;</span> len; l<span style=color:#719e07>++</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成参数类型进行检测代码,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; &amp;&amp; &#34;</span>).append(<span style=color:#2aa198>&#34; $3[&#34;</span>).append(l).append(<span style=color:#2aa198>&#34;].getName().equals(\&#34;&#34;</span>)
</span></span><span style=display:flex><span> .append(m.getParameterTypes()<span style=color:#719e07>[</span>l<span style=color:#719e07>]</span>.getName()).append(<span style=color:#2aa198>&#34;\&#34;)&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 ) {,完成方法判断语句,此时生成的代码可能如下(已格式化):</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if (&#34;sayHello&#34;.equals($2) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3.length == 2</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)) {</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; ) { &#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 根据返回值类型生成目标方法调用语句</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (m.getReturnType() <span style=color:#719e07>==</span> Void.TYPE)
</span></span><span style=display:flex><span> <span style=color:#586e75>// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; w.&#34;</span>).append(mn).append(<span style=color:#2aa198>&#39;(&#39;</span>).append(args(m.getParameterTypes(), <span style=color:#2aa198>&#34;$4&#34;</span>)).append(<span style=color:#2aa198>&#34;);&#34;</span>).append(<span style=color:#2aa198>&#34; return null;&#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>else</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; return ($w)w.&#34;</span>).append(mn).append(<span style=color:#2aa198>&#39;(&#39;</span>).append(args(m.getParameterTypes(), <span style=color:#2aa198>&#34;$4&#34;</span>)).append(<span style=color:#2aa198>&#34;);&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 }, 生成的代码形如(已格式化):</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if (&#34;sayHello&#34;.equals($2) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3.length == 2</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)) {</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>//</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); </span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// return null;</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// }</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加方法名到 mns 集合中</span>
</span></span><span style=display:flex><span> mns.add(mn);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测当前方法是否在 c 中被声明的</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (m.getDeclaringClass() <span style=color:#719e07>==</span> c)
</span></span><span style=display:flex><span> <span style=color:#586e75>// 若是,则将当前方法名添加到 dmns 中</span>
</span></span><span style=display:flex><span> dmns.add(mn);
</span></span><span style=display:flex><span> ms.put(ReflectUtils.getDesc(m), m);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (hasMethod) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加异常捕捉语句</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; } catch(Throwable e) { &#34;</span>);
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; throw new java.lang.reflect.InvocationTargetException(e); &#34;</span>);
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; }&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 NoSuchMethodException 异常抛出代码</span>
</span></span><span style=display:flex><span> c3.append(<span style=color:#2aa198>&#34; throw new &#34;</span> <span style=color:#719e07>+</span> NoSuchMethodException.class.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;(\&#34;Not found method \\\&#34;\&#34;+$2+\&#34;\\\&#34; in class &#34;</span> <span style=color:#719e07>+</span> c.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.\&#34;); }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// --------------------------------✨ 分割线3 ✨-------------------------------------</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> Matcher matcher;
</span></span><span style=display:flex><span> <span style=color:#586e75>// 处理 get/set 方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (Map.Entry<span style=color:#719e07>&lt;</span>String, Method<span style=color:#719e07>&gt;</span> entry : ms.entrySet()) {
</span></span><span style=display:flex><span> String md <span style=color:#719e07>=</span> entry.getKey();
</span></span><span style=display:flex><span> Method method <span style=color:#719e07>=</span> (Method) entry.getValue();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 匹配以 get 开头的方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> ((matcher <span style=color:#719e07>=</span> ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取属性名</span>
</span></span><span style=display:flex><span> String pn <span style=color:#719e07>=</span> propertyName(matcher.group(1));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成属性判断以及返回语句,示例如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;name&#34;) ) { return ($w).w.getName(); }</span>
</span></span><span style=display:flex><span> c2.append(<span style=color:#2aa198>&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style=color:#2aa198>&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(method.getName()).append(<span style=color:#2aa198>&#34;(); }&#34;</span>);
</span></span><span style=display:flex><span> pts.put(pn, method.getReturnType());
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 匹配以 is/has/can 开头的方法</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> ((matcher <span style=color:#719e07>=</span> ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
</span></span><span style=display:flex><span> String pn <span style=color:#719e07>=</span> propertyName(matcher.group(1));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成属性判断以及返回语句,示例如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;dream&#34;) ) { return ($w).w.hasDream(); }</span>
</span></span><span style=display:flex><span> c2.append(<span style=color:#2aa198>&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style=color:#2aa198>&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(method.getName()).append(<span style=color:#2aa198>&#34;(); }&#34;</span>);
</span></span><span style=display:flex><span> pts.put(pn, method.getReturnType());
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 匹配以 set 开头的方法</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> ((matcher <span style=color:#719e07>=</span> ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;</span> pt <span style=color:#719e07>=</span> method.getParameterTypes()<span style=color:#719e07>[</span>0<span style=color:#719e07>]</span>;
</span></span><span style=display:flex><span> String pn <span style=color:#719e07>=</span> propertyName(matcher.group(1));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成属性判断以及 setter 调用语句,示例如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// if( $2.equals(&#34;name&#34;) ) { w.setName((java.lang.String)$3); return; }</span>
</span></span><span style=display:flex><span> c1.append(<span style=color:#2aa198>&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style=color:#2aa198>&#34;\&#34;) ){ w.&#34;</span>).append(method.getName()).append(<span style=color:#2aa198>&#34;(&#34;</span>).append(arg(pt, <span style=color:#2aa198>&#34;$3&#34;</span>)).append(<span style=color:#2aa198>&#34;); return; }&#34;</span>);
</span></span><span style=display:flex><span> pts.put(pn, pt);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加 NoSuchPropertyException 异常抛出代码</span>
</span></span><span style=display:flex><span> c1.append(<span style=color:#2aa198>&#34; throw new &#34;</span> <span style=color:#719e07>+</span> NoSuchPropertyException.class.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;(\&#34;Not found property \\\&#34;\&#34;+$2+\&#34;\\\&#34; filed or setter method in class &#34;</span> <span style=color:#719e07>+</span> c.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.\&#34;); }&#34;</span>);
</span></span><span style=display:flex><span> c2.append(<span style=color:#2aa198>&#34; throw new &#34;</span> <span style=color:#719e07>+</span> NoSuchPropertyException.class.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;(\&#34;Not found property \\\&#34;\&#34;+$2+\&#34;\\\&#34; filed or setter method in class &#34;</span> <span style=color:#719e07>+</span> c.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;.\&#34;); }&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// --------------------------------✨ 分割线4 ✨-------------------------------------</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>long</span> id <span style=color:#719e07>=</span> WRAPPER_CLASS_COUNTER.getAndIncrement();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建类生成器</span>
</span></span><span style=display:flex><span> ClassGenerator cc <span style=color:#719e07>=</span> ClassGenerator.newInstance(cl);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置类名及超类</span>
</span></span><span style=display:flex><span> cc.setClassName((Modifier.isPublic(c.getModifiers()) <span style=color:#719e07>?</span> Wrapper.class.getName() : c.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;$sw&#34;</span>) <span style=color:#719e07>+</span> id);
</span></span><span style=display:flex><span> cc.setSuperClass(Wrapper.class);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加默认构造方法</span>
</span></span><span style=display:flex><span> cc.addDefaultConstructor();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加字段</span>
</span></span><span style=display:flex><span> cc.addField(<span style=color:#2aa198>&#34;public static String[] pns;&#34;</span>);
</span></span><span style=display:flex><span> cc.addField(<span style=color:#2aa198>&#34;public static &#34;</span> <span style=color:#719e07>+</span> Map.class.getName() <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34; pts;&#34;</span>);
</span></span><span style=display:flex><span> cc.addField(<span style=color:#2aa198>&#34;public static String[] mns;&#34;</span>);
</span></span><span style=display:flex><span> cc.addField(<span style=color:#2aa198>&#34;public static String[] dmns;&#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (<span style=color:#dc322f>int</span> i <span style=color:#719e07>=</span> 0, len <span style=color:#719e07>=</span> ms.size(); i <span style=color:#719e07>&lt;</span> len; i<span style=color:#719e07>++</span>)
</span></span><span style=display:flex><span> cc.addField(<span style=color:#2aa198>&#34;public static Class[] mts&#34;</span> <span style=color:#719e07>+</span> i <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;;&#34;</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加方法代码</span>
</span></span><span style=display:flex><span> cc.addMethod(<span style=color:#2aa198>&#34;public String[] getPropertyNames(){ return pns; }&#34;</span>);
</span></span><span style=display:flex><span> cc.addMethod(<span style=color:#2aa198>&#34;public boolean hasProperty(String n){ return pts.containsKey($1); }&#34;</span>);
</span></span><span style=display:flex><span> cc.addMethod(<span style=color:#2aa198>&#34;public Class getPropertyType(String n){ return (Class)pts.get($1); }&#34;</span>);
</span></span><span style=display:flex><span> cc.addMethod(<span style=color:#2aa198>&#34;public String[] getMethodNames(){ return mns; }&#34;</span>);
</span></span><span style=display:flex><span> cc.addMethod(<span style=color:#2aa198>&#34;public String[] getDeclaredMethodNames(){ return dmns; }&#34;</span>);
</span></span><span style=display:flex><span> cc.addMethod(c1.toString());
</span></span><span style=display:flex><span> cc.addMethod(c2.toString());
</span></span><span style=display:flex><span> cc.addMethod(c3.toString());
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 生成类</span>
</span></span><span style=display:flex><span> Class<span style=color:#719e07>&lt;?&gt;</span> wc <span style=color:#719e07>=</span> cc.toClass();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置字段值</span>
</span></span><span style=display:flex><span> wc.getField(<span style=color:#2aa198>&#34;pts&#34;</span>).set(<span style=color:#cb4b16>null</span>, pts);
</span></span><span style=display:flex><span> wc.getField(<span style=color:#2aa198>&#34;pns&#34;</span>).set(<span style=color:#cb4b16>null</span>, pts.keySet().toArray(<span style=color:#719e07>new</span> String<span style=color:#719e07>[</span>0<span style=color:#719e07>]</span>));
</span></span><span style=display:flex><span> wc.getField(<span style=color:#2aa198>&#34;mns&#34;</span>).set(<span style=color:#cb4b16>null</span>, mns.toArray(<span style=color:#719e07>new</span> String<span style=color:#719e07>[</span>0<span style=color:#719e07>]</span>));
</span></span><span style=display:flex><span> wc.getField(<span style=color:#2aa198>&#34;dmns&#34;</span>).set(<span style=color:#cb4b16>null</span>, dmns.toArray(<span style=color:#719e07>new</span> String<span style=color:#719e07>[</span>0<span style=color:#719e07>]</span>));
</span></span><span style=display:flex><span> <span style=color:#dc322f>int</span> ix <span style=color:#719e07>=</span> 0;
</span></span><span style=display:flex><span> <span style=color:#719e07>for</span> (Method m : ms.values())
</span></span><span style=display:flex><span> wc.getField(<span style=color:#2aa198>&#34;mts&#34;</span> <span style=color:#719e07>+</span> ix<span style=color:#719e07>++</span>).set(<span style=color:#cb4b16>null</span>, m.getParameterTypes());
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 Wrapper 实例</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> (Wrapper) wc.newInstance();
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (RuntimeException e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> e;
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Throwable e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RuntimeException(e.getMessage(), e);
</span></span><span style=display:flex><span> } <span style=color:#719e07>finally</span> {
</span></span><span style=display:flex><span> cc.release();
</span></span><span style=display:flex><span> ms.clear();
</span></span><span style=display:flex><span> mns.clear();
</span></span><span style=display:flex><span> dmns.clear();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码很长,大家耐心看一下。我们在上面代码中做了大量的注释,并按功能对代码进行了分块,以帮助大家理解代码逻辑。下面对这段代码进行讲解。首先我们把目光移到分割线1之上的代码,这段代码主要用于进行一些初始化操作。比如创建 c1、c2、c3 以及 pts、ms、mns 等变量,以及向 c1、c2、c3 中添加方法定义和类型转换代码。接下来是分割线1到分割线2之间的代码,这段代码用于为 public 级别的字段生成条件判断取值与赋值代码。这段代码不是很难看懂,就不多说了。继续向下看,分割线2和分隔线3之间的代码用于为定义在当前类中的方法生成判断语句,和方法调用语句。因为需要对方法重载进行校验,因此到这这段代码看起来有点复杂。不过耐心看一下,也不是很难理解。接下来是分割线3和分隔线4之间的代码,这段代码用于处理 getter、setter 以及以 is/has/can 开头的方法。处理方式是通过正则表达式获取方法类型(get/set/is/&mldr;),以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。最后我们再来看一下分隔线4以下的代码,这段代码通过 ClassGenerator 为刚刚生成的代码构建 Class 类,并通过反射创建对象。ClassGenerator 是 Dubbo 自己封装的,该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain),该方法通过 javassist 构建 Class。这里就不分析 toClass 方法了,大家请自行分析。</p><p>阅读 Wrapper 类代码需要对 javassist 框架有所了解。关于 javassist,大家如果不熟悉,请自行查阅资料,本节不打算介绍 javassist 相关内容。</p><p>好了,关于 Wrapper 类生成过程就分析到这。如果大家看的不是很明白,可以单独为 Wrapper 创建单元测试,然后单步调试。并将生成的代码拷贝出来,格式化后再进行观察和理解。</p><h3 id=222-导出服务到本地>2.2.2 导出服务到本地</h3><p>本节我们来看一下服务导出相关的代码,按照代码执行顺序,本节先来分析导出服务到本地的过程。相关代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>exportLocal</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
</span></span><span style=display:flex><span> URL local <span style=color:#719e07>=</span> URL.valueOf(url.toFullString())
</span></span><span style=display:flex><span> .setProtocol(Constants.LOCAL_PROTOCOL) <span style=color:#586e75>// 设置协议头为 injvm</span>
</span></span><span style=display:flex><span> .setHost(LOCALHOST)
</span></span><span style=display:flex><span> .setPort(0);
</span></span><span style=display:flex><span> ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法</span>
</span></span><span style=display:flex><span> Exporter<span style=color:#719e07>&lt;?&gt;</span> exporter <span style=color:#719e07>=</span> protocol.export(
</span></span><span style=display:flex><span> proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
</span></span><span style=display:flex><span> exporters.add(exporter);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>exportLocal 方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> Exporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>export</span>(Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> invoker) <span style=color:#268bd2>throws</span> RpcException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 InjvmExporter</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> InjvmExporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就分析完了,接下来,我们继续分析导出服务到远程的过程。</p><h3 id=223-导出服务到远程>2.2.3 导出服务到远程</h3><p>与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程。这两个过程涉及到了大量的调用,比较复杂。按照代码执行顺序,本节先来分析服务导出逻辑,服务注册逻辑将在下一节进行分析。下面开始分析,我们把目光移动到 RegistryProtocol 的 export 方法上。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> Exporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>export</span>(<span style=color:#268bd2>final</span> Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> originInvoker) <span style=color:#268bd2>throws</span> RpcException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 导出服务</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> exporter <span style=color:#719e07>=</span> doLocalExport(originInvoker);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&amp;dubbo=2.0.2&amp;export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider</span>
</span></span><span style=display:flex><span> URL registryUrl <span style=color:#719e07>=</span> getRegistryUrl(originInvoker);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> Registry registry <span style=color:#719e07>=</span> getRegistry(originInvoker);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取已注册的服务提供者 URL,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> URL registeredProviderUrl <span style=color:#719e07>=</span> getRegisteredProviderUrl(originInvoker);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 register 参数</span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> register <span style=color:#719e07>=</span> registeredProviderUrl.getParameter(<span style=color:#2aa198>&#34;register&#34;</span>, <span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 向服务提供者与消费者注册表中注册服务提供者</span>
</span></span><span style=display:flex><span> ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 根据 register 的值决定是否注册服务</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (register) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 向注册中心注册服务</span>
</span></span><span style=display:flex><span> register(registryUrl, registeredProviderUrl);
</span></span><span style=display:flex><span> ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取订阅 URL,比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&amp;check=false&amp;anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> URL overrideSubscribeUrl <span style=color:#719e07>=</span> getSubscribedOverrideUrl(registeredProviderUrl);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建监听器</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> OverrideListener overrideSubscribeListener <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> OverrideListener(overrideSubscribeUrl, originInvoker);
</span></span><span style=display:flex><span> overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 向注册中心进行订阅 override 数据</span>
</span></span><span style=display:flex><span> registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建并返回 DestroyableExporter</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> DestroyableExporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码看起来比较复杂,主要做如下一些操作:</p><ol><li>调用 doLocalExport 导出服务</li><li>向注册中心注册服务</li><li>向注册中心进行订阅 override 数据</li><li>创建并返回 DestroyableExporter</li></ol><p>在以上操作中,除了创建并返回 DestroyableExporter 没什么难度外,其他几步操作都不是很简单。这其中,导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据并非本文重点内容,后面会简单介绍一下。下面先来分析 doLocalExport 方法的逻辑,如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>doLocalExport</span>(<span style=color:#268bd2>final</span> Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> originInvoker) {
</span></span><span style=display:flex><span> String key <span style=color:#719e07>=</span> getCacheKey(originInvoker);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 访问缓存</span>
</span></span><span style=display:flex><span> ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> exporter <span style=color:#719e07>=</span> (ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>) bounds.get(key);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (exporter <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#268bd2>synchronized</span> (bounds) {
</span></span><span style=display:flex><span> exporter <span style=color:#719e07>=</span> (ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>) bounds.get(key);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (exporter <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 Invoker 为委托类对象</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> Invoker<span style=color:#719e07>&lt;?&gt;</span> invokerDelegete <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> InvokerDelegete<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>(originInvoker, getProviderUrl(originInvoker));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 调用 protocol 的 export 方法导出服务</span>
</span></span><span style=display:flex><span> exporter <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ExporterChangeableWrapper<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>((Exporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>) protocol.export(invokerDelegete), originInvoker);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 写缓存</span>
</span></span><span style=display:flex><span> bounds.put(key, exporter);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> exporter;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面的代码是典型的双重检查锁,大家在阅读 Dubbo 的源码中,会多次见到。接下来,我们把重点放在 Protocol 的 export 方法上。假设运行时协议为 dubbo,此处的 protocol 变量会在运行时加载 DubboProtocol,并调用 DubboProtocol 的 export 方法。所以,接下来我们目光转移到 DubboProtocol 的 export 方法上,相关分析如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> Exporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>export</span>(Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> invoker) <span style=color:#268bd2>throws</span> RpcException {
</span></span><span style=display:flex><span> URL url <span style=color:#719e07>=</span> invoker.getUrl();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880</span>
</span></span><span style=display:flex><span> String key <span style=color:#719e07>=</span> serviceKey(url);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 DubboExporter</span>
</span></span><span style=display:flex><span> DubboExporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> exporter <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> DubboExporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span>(invoker, key, exporterMap);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 将 &lt;key, exporter&gt; 键值对放入缓存中</span>
</span></span><span style=display:flex><span> exporterMap.put(key, exporter);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 本地存根相关代码</span>
</span></span><span style=display:flex><span> Boolean isStubSupportEvent <span style=color:#719e07>=</span> url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
</span></span><span style=display:flex><span> Boolean isCallbackservice <span style=color:#719e07>=</span> url.getParameter(Constants.IS_CALLBACK_SERVICE, <span style=color:#cb4b16>false</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (isStubSupportEvent <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>isCallbackservice) {
</span></span><span style=display:flex><span> String stubServiceMethods <span style=color:#719e07>=</span> url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (stubServiceMethods <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> stubServiceMethods.length() <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 省略日志打印代码</span>
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 启动服务器</span>
</span></span><span style=display:flex><span> openServer(url);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 优化序列化</span>
</span></span><span style=display:flex><span> optimizeSerialization(url);
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> exporter;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,我们重点关注 DubboExporter 的创建以及 openServer 方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。另外,DubboExporter 的代码比较简单,就不分析了。下面分析 openServer 方法。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>openServer</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例</span>
</span></span><span style=display:flex><span> String key <span style=color:#719e07>=</span> url.getAddress();
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> isServer <span style=color:#719e07>=</span> url.getParameter(Constants.IS_SERVER_KEY, <span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (isServer) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 访问缓存</span>
</span></span><span style=display:flex><span> ExchangeServer server <span style=color:#719e07>=</span> serverMap.get(key);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (server <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建服务器实例</span>
</span></span><span style=display:flex><span> serverMap.put(key, createServer(url));
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 服务器已创建,则根据 url 中的配置重置服务器</span>
</span></span><span style=display:flex><span> server.reset(url);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。考虑到篇幅问题,关于服务器实例重置的代码就不分析了。接下来分析服务器实例的创建过程。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>private</span> ExchangeServer <span style=color:#268bd2>createServer</span>(URL url) {
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加心跳检测配置到 url 中</span>
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 server 参数,默认为 netty</span>
</span></span><span style=display:flex><span> String str <span style=color:#719e07>=</span> url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (str <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> str.length() <span style=color:#719e07>&gt;</span> 0 <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RpcException(<span style=color:#2aa198>&#34;Unsupported server type: &#34;</span> <span style=color:#719e07>+</span> str <span style=color:#719e07>+</span> <span style=color:#2aa198>&#34;, url: &#34;</span> <span style=color:#719e07>+</span> url);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加编码解码器参数</span>
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
</span></span><span style=display:flex><span> ExchangeServer server;
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 ExchangeServer</span>
</span></span><span style=display:flex><span> server <span style=color:#719e07>=</span> Exchangers.bind(url, requestHandler);
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (RemotingException e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RpcException(<span style=color:#2aa198>&#34;Fail to start server...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 client 参数,可指定 netty,mina</span>
</span></span><span style=display:flex><span> str <span style=color:#719e07>=</span> url.getParameter(Constants.CLIENT_KEY);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (str <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> str.length() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]</span>
</span></span><span style=display:flex><span> Set<span style=color:#719e07>&lt;</span>String<span style=color:#719e07>&gt;</span> supportedTypes <span style=color:#719e07>=</span> ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 是否包含 client 所表示的 Transporter,若不包含,则抛出异常</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>supportedTypes.contains(str)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RpcException(<span style=color:#2aa198>&#34;Unsupported client type...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> server;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>static</span> ExchangeServer <span style=color:#268bd2>bind</span>(URL url, ExchangeHandler handler) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (url <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;url == null&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (handler <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;handler == null&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.addParameterIfAbsent(Constants.CODEC_KEY, <span style=color:#2aa198>&#34;exchange&#34;</span>);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 Exchanger,默认为 HeaderExchanger。</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> getExchanger(url).bind(url, handler);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码比较简单,就不多说了。下面看一下 HeaderExchanger 的 bind 方法。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> ExchangeServer <span style=color:#268bd2>bind</span>(URL url, ExchangeHandler handler) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 1. new HeaderExchangeHandler(handler)</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 2. new DecodeHandler(new HeaderExchangeHandler(handler))</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> HeaderExchangeServer(Transporters.bind(url, <span style=color:#719e07>new</span> DecodeHandler(<span style=color:#719e07>new</span> HeaderExchangeHandler(handler))));
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>HeaderExchanger 的 bind 方法包含的逻辑比较多,但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>static</span> Server <span style=color:#268bd2>bind</span>(URL url, ChannelHandler... handlers) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (url <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;url == null&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (handlers <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>||</span> handlers.length <span style=color:#719e07>==</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalArgumentException(<span style=color:#2aa198>&#34;handlers == null&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> ChannelHandler handler;
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (handlers.length <span style=color:#719e07>==</span> 1) {
</span></span><span style=display:flex><span> handler <span style=color:#719e07>=</span> handlers<span style=color:#719e07>[</span>0<span style=color:#719e07>]</span>;
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器</span>
</span></span><span style=display:flex><span> handler <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ChannelHandlerDispatcher(handlers);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取自适应 Transporter 实例,并调用实例方法</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> getTransporter().bind(url, handler);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 Transporter$Adaptive,也就是自适应拓展类。Transporter$Adaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。下面我们继续跟下去,这次分析的是 NettyTransporter 的 bind 方法。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> Server <span style=color:#268bd2>bind</span>(URL url, ChannelHandler listener) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 NettyServer</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> NettyServer(url, listener);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>这里仅有一句创建 NettyServer 的代码,无需多说,我们继续向下看。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>class</span> <span style=color:#268bd2>NettyServer</span> <span style=color:#268bd2>extends</span> AbstractServer <span style=color:#268bd2>implements</span> Server {
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#268bd2>NettyServer</span>(URL url, ChannelHandler handler) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 调用父类构造方法</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>super</span>(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>abstract</span> <span style=color:#268bd2>class</span> <span style=color:#268bd2>AbstractServer</span> <span style=color:#268bd2>extends</span> AbstractEndpoint <span style=color:#268bd2>implements</span> Server {
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#268bd2>AbstractServer</span>(URL url, ChannelHandler handler) <span style=color:#268bd2>throws</span> RemotingException {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 调用父类构造方法,这里就不用跟进去了,没什么复杂逻辑</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>super</span>(url, handler);
</span></span><span style=display:flex><span> localAddress <span style=color:#719e07>=</span> getUrl().toInetSocketAddress();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 ip 和端口</span>
</span></span><span style=display:flex><span> String bindIp <span style=color:#719e07>=</span> getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
</span></span><span style=display:flex><span> <span style=color:#dc322f>int</span> bindPort <span style=color:#719e07>=</span> getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (url.getParameter(Constants.ANYHOST_KEY, <span style=color:#cb4b16>false</span>) <span style=color:#719e07>||</span> NetUtils.isInvalidLocalHost(bindIp)) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置 ip 为 0.0.0.0</span>
</span></span><span style=display:flex><span> bindIp <span style=color:#719e07>=</span> NetUtils.ANYHOST;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> bindAddress <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> InetSocketAddress(bindIp, bindPort);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取最大可接受连接数</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>this</span>.accepts <span style=color:#719e07>=</span> url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
</span></span><span style=display:flex><span> <span style=color:#719e07>this</span>.idleTimeout <span style=color:#719e07>=</span> url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 调用模板方法 doOpen 启动服务器</span>
</span></span><span style=display:flex><span> doOpen();
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Throwable t) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RemotingException(<span style=color:#2aa198>&#34;Failed to bind &#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> DataStore dataStore <span style=color:#719e07>=</span> ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
</span></span><span style=display:flex><span> executor <span style=color:#719e07>=</span> (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>protected</span> <span style=color:#268bd2>abstract</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doOpen</span>() <span style=color:#268bd2>throws</span> Throwable;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>protected</span> <span style=color:#268bd2>abstract</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doClose</span>() <span style=color:#268bd2>throws</span> Throwable;
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面代码多为赋值代码,不需要多讲。我们重点关注 doOpen 抽象方法,该方法需要子类实现。下面回到 NettyServer 中。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>protected</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doOpen</span>() <span style=color:#268bd2>throws</span> Throwable {
</span></span><span style=display:flex><span> NettyHelper.setNettyLoggerFactory();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 boss 和 worker 线程池</span>
</span></span><span style=display:flex><span> ExecutorService boss <span style=color:#719e07>=</span> Executors.newCachedThreadPool(<span style=color:#719e07>new</span> NamedThreadFactory(<span style=color:#2aa198>&#34;NettyServerBoss&#34;</span>, <span style=color:#cb4b16>true</span>));
</span></span><span style=display:flex><span> ExecutorService worker <span style=color:#719e07>=</span> Executors.newCachedThreadPool(<span style=color:#719e07>new</span> NamedThreadFactory(<span style=color:#2aa198>&#34;NettyServerWorker&#34;</span>, <span style=color:#cb4b16>true</span>));
</span></span><span style=display:flex><span> ChannelFactory channelFactory <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 ServerBootstrap</span>
</span></span><span style=display:flex><span> bootstrap <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> ServerBootstrap(channelFactory);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> NettyHandler nettyHandler <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> NettyHandler(getUrl(), <span style=color:#719e07>this</span>);
</span></span><span style=display:flex><span> channels <span style=color:#719e07>=</span> nettyHandler.getChannels();
</span></span><span style=display:flex><span> bootstrap.setOption(<span style=color:#2aa198>&#34;child.tcpNoDelay&#34;</span>, <span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 设置 PipelineFactory</span>
</span></span><span style=display:flex><span> bootstrap.setPipelineFactory(<span style=color:#719e07>new</span> ChannelPipelineFactory() {
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> ChannelPipeline <span style=color:#268bd2>getPipeline</span>() {
</span></span><span style=display:flex><span> NettyCodecAdapter adapter <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
</span></span><span style=display:flex><span> ChannelPipeline pipeline <span style=color:#719e07>=</span> Channels.pipeline();
</span></span><span style=display:flex><span> pipeline.addLast(<span style=color:#2aa198>&#34;decoder&#34;</span>, adapter.getDecoder());
</span></span><span style=display:flex><span> pipeline.addLast(<span style=color:#2aa198>&#34;encoder&#34;</span>, adapter.getEncoder());
</span></span><span style=display:flex><span> pipeline.addLast(<span style=color:#2aa198>&#34;handler&#34;</span>, nettyHandler);
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> pipeline;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> });
</span></span><span style=display:flex><span> <span style=color:#586e75>// 绑定到指定的 ip 和端口上</span>
</span></span><span style=display:flex><span> channel <span style=color:#719e07>=</span> bootstrap.bind(getBindAddress());
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>以上就是 NettyServer 创建的过程,dubbo 默认使用的 NettyServer 是基于 netty 3.x 版本实现的,比较老了。因此 Dubbo 另外提供了 netty 4.x 版本的 NettyServer,大家可在使用 Dubbo 的过程中按需进行配置。</p><p>到此,关于服务导出的过程就分析完了。整个过程比较复杂,大家在分析的过程中耐心一些。并且多写 Demo 进行调试,以便能够更好的理解代码逻辑。</p><p>本节内容先到这里,接下来分析服务导出的另一块逻辑 — 服务注册。</p><h3 id=224-服务注册>2.2.4 服务注册</h3><p>本节我们来分析服务注册过程,服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做,直连方式不利于服务治理,仅推荐在测试服务时使用。对于 Dubbo 来说,注册中心虽不是必需,但却是必要的。因此,关于注册中心以及服务注册相关逻辑,我们也需要搞懂。</p><p>本节内容以 Zookeeper 注册中心作为分析目标,其他类型注册中心大家可自行分析。下面从服务注册的入口方法开始分析,我们把目光再次移到 RegistryProtocol 的 export 方法上。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> Exporter<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> <span style=color:#268bd2>export</span>(<span style=color:#268bd2>final</span> Invoker<span style=color:#719e07>&lt;</span>T<span style=color:#719e07>&gt;</span> originInvoker) <span style=color:#268bd2>throws</span> RpcException {
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// ${导出服务}</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 省略其他代码</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> register <span style=color:#719e07>=</span> registeredProviderUrl.getParameter(<span style=color:#2aa198>&#34;register&#34;</span>, <span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (register) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 注册服务</span>
</span></span><span style=display:flex><span> register(registryUrl, registeredProviderUrl);
</span></span><span style=display:flex><span> ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span style=color:#cb4b16>true</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> URL overrideSubscribeUrl <span style=color:#719e07>=</span> getSubscribedOverrideUrl(registeredProviderUrl);
</span></span><span style=display:flex><span> <span style=color:#268bd2>final</span> OverrideListener overrideSubscribeListener <span style=color:#719e07>=</span> <span style=color:#719e07>new</span> OverrideListener(overrideSubscribeUrl, originInvoker);
</span></span><span style=display:flex><span> overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 订阅 override 数据</span>
</span></span><span style=display:flex><span> registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 省略部分代码</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>RegistryProtocol 的 export 方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>register</span>(URL registryUrl, URL registedProviderUrl) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 Registry</span>
</span></span><span style=display:flex><span> Registry registry <span style=color:#719e07>=</span> registryFactory.getRegistry(registryUrl);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 注册服务</span>
</span></span><span style=display:flex><span> registry.register(registedProviderUrl);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>register 方法包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。接下来分两节内容对这两步操作进行分析。</p><h4 id=2241-创建注册中心>2.2.4.1 创建注册中心</h4><p>本节内容以 Zookeeper 注册中心为例进行分析。下面先来看一下 getRegistry 方法的源码,这个方法由 AbstractRegistryFactory 实现。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> Registry <span style=color:#268bd2>getRegistry</span>(URL url) {
</span></span><span style=display:flex><span> url <span style=color:#719e07>=</span> url.setPath(RegistryService.class.getName())
</span></span><span style=display:flex><span> .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
</span></span><span style=display:flex><span> .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
</span></span><span style=display:flex><span> String key <span style=color:#719e07>=</span> url.toServiceString();
</span></span><span style=display:flex><span> LOCK.lock();
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 访问缓存</span>
</span></span><span style=display:flex><span> Registry registry <span style=color:#719e07>=</span> REGISTRIES.get(key);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registry <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> registry;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 缓存未命中,创建 Registry 实例</span>
</span></span><span style=display:flex><span> registry <span style=color:#719e07>=</span> createRegistry(url);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (registry <span style=color:#719e07>==</span> <span style=color:#cb4b16>null</span>) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;Can not create registry...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 写入缓存</span>
</span></span><span style=display:flex><span> REGISTRIES.put(key, registry);
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> registry;
</span></span><span style=display:flex><span> } <span style=color:#719e07>finally</span> {
</span></span><span style=display:flex><span> LOCK.unlock();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#268bd2>protected</span> <span style=color:#268bd2>abstract</span> Registry <span style=color:#268bd2>createRegistry</span>(URL url);
</span></span></code></pre></div><p>如上,getRegistry 方法先访问缓存,缓存未命中则调用 createRegistry 创建 Registry,然后写入缓存。这里的 createRegistry 是一个模板方法,由具体的子类实现。因此,下面我们到 ZookeeperRegistryFactory 中探究一番。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>class</span> <span style=color:#268bd2>ZookeeperRegistryFactory</span> <span style=color:#268bd2>extends</span> AbstractRegistryFactory {
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// zookeeperTransporter 由 SPI 在运行时注入,类型为 ZookeeperTransporter$Adaptive</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>private</span> ZookeeperTransporter zookeeperTransporter;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>setZookeeperTransporter</span>(ZookeeperTransporter zookeeperTransporter) {
</span></span><span style=display:flex><span> <span style=color:#719e07>this</span>.zookeeperTransporter <span style=color:#719e07>=</span> zookeeperTransporter;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> Registry <span style=color:#268bd2>createRegistry</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 ZookeeperRegistry</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> ZookeeperRegistry(url, zookeeperTransporter);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>ZookeeperRegistryFactory 的 createRegistry 方法仅包含一句代码,无需解释,继续跟下去。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>ZookeeperRegistry</span>(URL url, ZookeeperTransporter zookeeperTransporter) {
</span></span><span style=display:flex><span> <span style=color:#268bd2>super</span>(url);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (url.isAnyHost()) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;registry address == null&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取组名,默认为 dubbo</span>
</span></span><span style=display:flex><span> String group <span style=color:#719e07>=</span> url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>group.startsWith(Constants.PATH_SEPARATOR)) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// group = &#34;/&#34; + group</span>
</span></span><span style=display:flex><span> group <span style=color:#719e07>=</span> Constants.PATH_SEPARATOR <span style=color:#719e07>+</span> group;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>this</span>.root <span style=color:#719e07>=</span> group;
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporter</span>
</span></span><span style=display:flex><span> zkClient <span style=color:#719e07>=</span> zookeeperTransporter.connect(url);
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加状态监听器</span>
</span></span><span style=display:flex><span> zkClient.addStateListener(<span style=color:#719e07>new</span> StateListener() {
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>stateChanged</span>(<span style=color:#dc322f>int</span> state) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (state <span style=color:#719e07>==</span> RECONNECTED) {
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> recover();
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Exception e) {
</span></span><span style=display:flex><span> logger.error(e.getMessage(), e);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> });
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>在上面的代码代码中,我们重点关注 ZookeeperTransporter 的 connect 方法调用,这个方法用于创建 Zookeeper 客户端。创建好 Zookeeper 客户端,意味着注册中心的创建过程就结束了。接下来,再来分析一下 Zookeeper 客户端的创建过程。</p><p>前面说过,这里的 zookeeperTransporter 类型为自适应拓展类,因此 connect 方法会在被调用时决定加载什么类型的 ZookeeperTransporter 拓展,默认为 CuratorZookeeperTransporter。下面我们到 CuratorZookeeperTransporter 中看一看。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> ZookeeperClient <span style=color:#268bd2>connect</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 CuratorZookeeperClient</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span> <span style=color:#719e07>new</span> CuratorZookeeperClient(url);
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>继续向下看。</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#268bd2>class</span> <span style=color:#268bd2>CuratorZookeeperClient</span> <span style=color:#268bd2>extends</span> AbstractZookeeperClient<span style=color:#719e07>&lt;</span>CuratorWatcher<span style=color:#719e07>&gt;</span> {
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>private</span> <span style=color:#268bd2>final</span> CuratorFramework client;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#268bd2>CuratorZookeeperClient</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#268bd2>super</span>(url);
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 创建 CuratorFramework 构造器</span>
</span></span><span style=display:flex><span> CuratorFrameworkFactory.Builder builder <span style=color:#719e07>=</span> CuratorFrameworkFactory.builder()
</span></span><span style=display:flex><span> .connectString(url.getBackupAddress())
</span></span><span style=display:flex><span> .retryPolicy(<span style=color:#719e07>new</span> RetryNTimes(1, 1000))
</span></span><span style=display:flex><span> .connectionTimeoutMs(5000);
</span></span><span style=display:flex><span> String authority <span style=color:#719e07>=</span> url.getAuthority();
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (authority <span style=color:#719e07>!=</span> <span style=color:#cb4b16>null</span> <span style=color:#719e07>&amp;&amp;</span> authority.length() <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> builder <span style=color:#719e07>=</span> builder.authorization(<span style=color:#2aa198>&#34;digest&#34;</span>, authority.getBytes());
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#586e75>// 构建 CuratorFramework 实例</span>
</span></span><span style=display:flex><span> client <span style=color:#719e07>=</span> builder.build();
</span></span><span style=display:flex><span> <span style=color:#586e75>// 添加监听器</span>
</span></span><span style=display:flex><span> client.getConnectionStateListenable().addListener(<span style=color:#719e07>new</span> ConnectionStateListener() {
</span></span><span style=display:flex><span> <span style=color:#268bd2>@Override</span>
</span></span><span style=display:flex><span> <span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>stateChanged</span>(CuratorFramework client, ConnectionState state) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (state <span style=color:#719e07>==</span> ConnectionState.LOST) {
</span></span><span style=display:flex><span> CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> (state <span style=color:#719e07>==</span> ConnectionState.CONNECTED) {
</span></span><span style=display:flex><span> CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> <span style=color:#719e07>if</span> (state <span style=color:#719e07>==</span> ConnectionState.RECONNECTED) {
</span></span><span style=display:flex><span> CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> });
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 启动客户端</span>
</span></span><span style=display:flex><span> client.start();
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Exception e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(e.getMessage(), e);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>CuratorZookeeperClient 构造方法主要用于创建和启动 CuratorFramework 实例。以上基本上都是 Curator 框架的代码,大家如果对 Curator 框架不是很了解,可以参考 Curator 官方文档。</p><p>本节分析了 ZookeeperRegistry 实例的创建过程,整个过程并不是很复杂。大家在看完分析后,可以自行调试,以加深理解。现在注册中心实例创建好了,接下来要做的事情是向注册中心注册服务,我们继续往下看。</p><h4 id=2242-节点创建>2.2.4.2 节点创建</h4><p>以 Zookeeper 为例,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。为了让大家有一个直观的了解,下面我们将 Dubbo 的 demo 跑起来,然后通过 Zookeeper 可视化客户端 <a href=https://github.com/apache/zookeeper/tree/b79af153d0f98a4f3f3516910ed47234d7b3d74e/src/contrib/zooinspector>ZooInspector</a> 查看节点数据。如下:</p><p><img src=/imgs/dev/service-registry.png alt=img></p><p>从上图中可以看到 com.alibaba.dubbo.demo.DemoService 这个服务对应的配置信息(存储在 URL 中)最终被注册到了 /dubbo/com.alibaba.dubbo.demo.DemoService/providers/ 节点下。搞懂了服务注册的本质,那么接下来我们就可以去阅读服务注册的代码了。服务注册的接口为 register(URL),这个方法定义在 FailbackRegistry 抽象类中。代码如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>register</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#268bd2>super</span>.register(url);
</span></span><span style=display:flex><span> failedRegistered.remove(url);
</span></span><span style=display:flex><span> failedUnregistered.remove(url);
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 模板方法,由子类实现</span>
</span></span><span style=display:flex><span> doRegister(url);
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Exception e) {
</span></span><span style=display:flex><span> Throwable t <span style=color:#719e07>=</span> e;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 获取 check 参数,若 check = true 将会直接抛出异常</span>
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> check <span style=color:#719e07>=</span> getUrl().getParameter(Constants.CHECK_KEY, <span style=color:#cb4b16>true</span>)
</span></span><span style=display:flex><span> <span style=color:#719e07>&amp;&amp;</span> url.getParameter(Constants.CHECK_KEY, <span style=color:#cb4b16>true</span>)
</span></span><span style=display:flex><span> <span style=color:#719e07>&amp;&amp;</span> <span style=color:#719e07>!</span>Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
</span></span><span style=display:flex><span> <span style=color:#dc322f>boolean</span> skipFailback <span style=color:#719e07>=</span> t <span style=color:#719e07>instanceof</span> SkipFailbackWrapperException;
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (check <span style=color:#719e07>||</span> skipFailback) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (skipFailback) {
</span></span><span style=display:flex><span> t <span style=color:#719e07>=</span> t.getCause();
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(<span style=color:#2aa198>&#34;Failed to register&#34;</span>);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> logger.error(<span style=color:#2aa198>&#34;Failed to register&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 记录注册失败的链接</span>
</span></span><span style=display:flex><span> failedRegistered.add(url);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#268bd2>protected</span> <span style=color:#268bd2>abstract</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doRegister</span>(URL url);
</span></span></code></pre></div><p>如上,我们重点关注 doRegister 方法调用即可,其他的代码先忽略。doRegister 方法是一个模板方法,因此我们到 FailbackRegistry 子类 ZookeeperRegistry 中进行分析。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>protected</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>doRegister</span>(URL url) {
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// /${group}/${serviceInterface}/providers/${url}</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 比如</span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......</span>
</span></span><span style=display:flex><span> zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, <span style=color:#cb4b16>true</span>));
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Throwable e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> RpcException(<span style=color:#2aa198>&#34;Failed to register...&#34;</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>如上,ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成,该方法逻辑不难理解,就不分析了。接下来分析 create 方法,如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>create</span>(String path, <span style=color:#dc322f>boolean</span> ephemeral) {
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (<span style=color:#719e07>!</span>ephemeral) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (checkExists(path)) {
</span></span><span style=display:flex><span> <span style=color:#719e07>return</span>;
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> <span style=color:#dc322f>int</span> i <span style=color:#719e07>=</span> path.lastIndexOf(<span style=color:#2aa198>&#39;/&#39;</span>);
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (i <span style=color:#719e07>&gt;</span> 0) {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 递归创建上一级路径</span>
</span></span><span style=display:flex><span> create(path.substring(0, i), <span style=color:#cb4b16>false</span>);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#586e75>// 根据 ephemeral 的值创建临时或持久节点</span>
</span></span><span style=display:flex><span> <span style=color:#719e07>if</span> (ephemeral) {
</span></span><span style=display:flex><span> createEphemeral(path);
</span></span><span style=display:flex><span> } <span style=color:#719e07>else</span> {
</span></span><span style=display:flex><span> createPersistent(path);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>上面方法先是通过递归创建当前节点的上一级路径,然后再根据 ephemeral 的值决定创建临时还是持久节点。createEphemeral 和 createPersistent 这两个方法都比较简单,这里简单分析其中的一个。如下:</p><div class=highlight><pre tabindex=0 style=color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#268bd2>public</span> <span style=color:#dc322f>void</span> <span style=color:#268bd2>createEphemeral</span>(String path) {
</span></span><span style=display:flex><span> <span style=color:#719e07>try</span> {
</span></span><span style=display:flex><span> <span style=color:#586e75>// 通过 Curator 框架创建节点</span>
</span></span><span style=display:flex><span> client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (NodeExistsException e) {
</span></span><span style=display:flex><span> } <span style=color:#719e07>catch</span> (Exception e) {
</span></span><span style=display:flex><span> <span style=color:#719e07>throw</span> <span style=color:#719e07>new</span> IllegalStateException(e.getMessage(), e);
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。本节先到这,接下来分析数据订阅过程。</p><h3 id=225-订阅-override-数据>2.2.5 订阅 override 数据</h3><p>// 待补充</p><h2 id=3总结>3.总结</h2><p>本篇文章详细分析了 Dubbo 服务导出过程,包括配置检测,URL 组装,Invoker 创建过程、导出服务以及注册服务等等。篇幅比较大,需要大家耐心阅读。本篇文章先就到这,如果文章有不妥错误之处,希望大家能够进行反馈或修正。</p><div id=pre-footer><h2>反馈</h2><p class=feedback--prompt>此页是否对您有帮助?</p><button class="btn btn-primary mb-4 feedback--yes"></button>
<button class="btn btn-primary mb-4 feedback--no"></button></div><script>const yes=document.querySelector(".feedback--yes"),no=document.querySelector(".feedback--no");document.querySelectorAll(".feedback--link").forEach(e=>{e.href=e.href+window.location.pathname});const sendFeedback=e=>{gtag||console.log("!gtag"),gtag("event","click",{event_category:"Helpful",event_label:window.location.pathname,value:e})},disableButtons=()=>{yes.disabled=!0,yes.classList.add("feedback--button__disabled"),no.disabled=!0,no.classList.add("feedback--button__disabled")};yes.addEventListener("click",()=>{sendFeedback(1),disableButtons(),document.querySelector(".feedback--response").classList.remove("feedback--response__hidden")}),no.addEventListener("click",()=>{sendFeedback(0),disableButtons(),document.querySelector(".feedback--response").classList.remove("feedback--response__hidden")})</script><br><div class="text-muted mt-5 pt-3 border-top">最后修改 March 1, 2023: <a href=https://github.com/apache/dubbo-website/commit/f2070b3fdd1b7b200db0f8f2a50fb09d7c398a64>Add /zh/ docs aliases (#2336) (f2070b3fdd1)</a></div></div></main><div class="d-none d-xl-block col-xl-2 td-toc d-print-none"><div class="td-page-meta ml-2 pb-1 pt-2 mb-0"><a href=https://github.com/apache/dubbo-website/edit/master/content/zh-cn/docsv2.7/dev/source/export-service.md target=_blank><i class="fa fa-edit fa-fw"></i> 编辑此页</a>
<a href="https://github.com/apache/dubbo-website/new/master/content/zh-cn/docsv2.7/dev/source/export-service.md?filename=change-me.md&amp;value=---%0Atitle%3A+%22Long+Page+Title%22%0AlinkTitle%3A+%22Short+Nav+Title%22%0Aweight%3A+100%0Adescription%3A+%3E-%0A+++++Page+description+for+heading+and+indexes.%0A---%0A%0A%23%23+Heading%0A%0AEdit+this+template+to+create+your+new+page.%0A%0A%2A+Give+it+a+good+name%2C+ending+in+%60.md%60+-+e.g.+%60getting-started.md%60%0A%2A+Edit+the+%22front+matter%22+section+at+the+top+of+the+page+%28weight+controls+how+its+ordered+amongst+other+pages+in+the+same+directory%3B+lowest+number+first%29.%0A%2A+Add+a+good+commit+message+at+the+bottom+of+the+page+%28%3C80+characters%3B+use+the+extended+description+field+for+more+detail%29.%0A%2A+Create+a+new+branch+so+you+can+preview+your+new+file+and+request+a+review+via+Pull+Request.%0A" target=_blank><i class="fa fa-edit fa-fw"></i> 创建子页面</a>
<a href="https://github.com/apache/dubbo-website/issues/new?title=%e6%9c%8d%e5%8a%a1%e5%af%bc%e5%87%ba" target=_blank><i class="fab fa-github fa-fw"></i> 登记问题</a>
<a href=https://github.com/apache/dubbo/issues/new target=_blank><i class="fas fa-tasks fa-fw"></i> 提交项目问题</a></div><nav id=TableOfContents><ul><li><a href=#1简介>1.简介</a></li><li><a href=#2源码分析>2.源码分析</a><ul><li><a href=#21-前置工作>2.1 前置工作</a></li><li><a href=#22-导出-dubbo-服务>2.2 导出 Dubbo 服务</a></li><li><a href=#221-invoker-创建过程>2.2.1 Invoker 创建过程</a></li><li><a href=#222-导出服务到本地>2.2.2 导出服务到本地</a></li><li><a href=#223-导出服务到远程>2.2.3 导出服务到远程</a></li><li><a href=#224-服务注册>2.2.4 服务注册</a></li><li><a href=#225-订阅-override-数据>2.2.5 订阅 override 数据</a></li></ul></li><li><a href=#3总结>3.总结</a></li></ul></nav></div></div></div></div><footer class="bg-dark py-5 row d-print-none footer-margin-0"><div class="container-fluid mx-sm-5"><div class=row><div class="col-6 col-sm-4 text-xs-center order-sm-2"><ul class="list-inline mb-0"><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title="Dubbo mailing list archive" aria-label="Dubbo mailing list archive"><a class=text-white target=_blank rel="noopener noreferrer" href=https://lists.apache.org/list.html?dev@dubbo.apache.org><i class="fa fa-envelope"></i></a></li></ul></div><div class="col-6 col-sm-4 text-right text-xs-center order-sm-3"><ul class="list-inline mb-0"><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title=GitHub aria-label=GitHub><a class=text-white target=_blank rel="noopener noreferrer" href=https://github.com/apache/dubbo><i class="fab fa-github"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title="Subscribe to mailing list" aria-label="Subscribe to mailing list"><a class=text-white target=_blank rel="noopener noreferrer" href=mailto:dev-subscribe@dubbo.apache.org><i class="fa fa-envelope"></i></a></li></ul></div><div class="col-12 col-sm-4 text-center py-2 order-sm-2"><small class=text-white>&copy; 2024 The Apache Software Foundation. Apache and the Apache feather logo are trademarks of The Apache Software Foundation. 保留所有权利</small></div></div></div></footer><div class="row pt-2 pb-2 footer-margin-0"><div class="container-fluid mx-sm-5"><div class=text-center id=my-footer><img alt=apache_logo src=/imgs/apache_logo.png><ul><li><a href=https://www.apache.org>Foundation</a></li><li><a href=https://www.apache.org/licenses/>License</a></li><li><a href=https://dubbo.apache.org/en/overview/notices/>Security</a></li><li><a href=https://www.apache.org/events/current-event>Events</a></li><li><a href=https://www.apache.org/foundation/sponsorship.html>Sponsorship</a></li><li><a href=https://privacy.apache.org/policies/privacy-policy-public.html>Privacy</a></li><li><a href=https://www.apache.org/foundation/thanks.html>Thanks</a></li></ul></div></div></div><script src=/js/popper.min.js integrity=sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49 crossorigin=anonymous></script><script src=/js/bootstrap.min.js integrity=sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy crossorigin=anonymous></script><script src=/js/main.min.b075178d232d3b0039b3cb6af2fc2e9d90071820167a60f4eea3a79169975ee8.js integrity="sha256-sHUXjSMtOwA5s8tq8vwunZAHGCAWemD07qOnkWmXXug=" crossorigin=anonymous></script><script async src=https://widget.kapa.ai/kapa-widget.bundle.js data-website-id=d763c4f2-f871-400b-aeca-d986c4af73c2 data-project-name="Apache Dubbo" data-project-color=#E8442E data-button-text="Ask AI" data-search-mode-enabled=true data-modal-open-on-command-k=true data-modal-disclaimer="The AI supports multiple languages, but it may not be accessible in China due to recaptcha, a proxy is required." data-project-logo=https://pbs.twimg.com/profile_images/1011849068283191302/FJbH5vbF_400x400.jpg data-modal-example-questions="What is Apache Dubbo?,How to run Apache Dubbo?" data-button-position-top data-button-position-right=20px data-button-position-bottom=200px data-button-position-left></script><script>(function(e,t,n,s){e[s]=e[s]||[];var a=t.getElementsByTagName(n)[0],i=t.createElement(n);i.async=!0,i.id="beacon-aplus",i.setAttribute("exparams","userid=&aplus&sidx=aplusSidex&ckx=aplusCkx"),i.src="//g.alicdn.com/alilog/mlog/aplus_v2.js",i.crossorigin="anonymous",a.parentNode.insertBefore(i,a)})(window,document,"script","aplus_queue"),function(e){var t=e.createElement("script");t.type="text/javascript",t.async=!0,t.src="//g.alicdn.com/aes/??tracker/3.3.4/index.js,tracker-plugin-pv/3.0.5/index.js,tracker-plugin-event/3.0.0/index.js,tracker-plugin-autolog/3.0.3/index.js,tracker-plugin-survey/3.0.3/index.js,tracker-plugin-jserror/3.0.3/index.js,tracker-plugin-resourceError/3.0.3/index.js",t.onload=function(){window.AES_CONFIG=window.AES_CONFIG||{env:"prod"},window.aes=new AES({pid:"zN245h",user_type:6}),window.AESPluginAutologConfig={exposure:"auto"},window.AEMPluginInstances=[aes.use(AESPluginPV,window.AESPluginPVConfig||{enableHistory:!0}),aes.use(AESPluginEvent,window.AESPluginEventConfig||{}),aes.use(AESPluginSurvey,window.AESPluginEventConfig||{}),aes.use(AESPluginAutolog,window.AESPluginAutologConfig||{}),aes.use(AESPluginJSError,window.AESPluginJSError||{}),aes.use(AESPluginResourceError,window.AESPluginResourceError||{})]},setTimeout(function(){e.getElementsByTagName("body")[0].appendChild(t)},800)}(document)</script></body></html>