前言

源码入手

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

前文 Dubbo 对配置文件的解析我们知道,服务(beans->dubbo:service)相关的节点配置会在com.alibaba.dubbo.config.spring.ServiceBean的实例中保存,这里正好巧了public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
出去一些初始化参数的工作,这里我们主要关注这里

public void onApplicationEvent(ApplicationEvent event) {if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {if (isDelay() && ! isExported() && ! isUnexported()) {if (logger.isInfoEnabled()) {logger.info("The service ready on spring started. service: " + getInterface());}//暴露服务export();}}}

public synchronized void export() 方法里会做一些判断,我们这里只关注自己最关注的部分

public synchronized void export() {//.......如果当前服务被暴露过就不再暴露,等一些判断if (delay != null && delay > 0) {//delay 延迟暴露参数,如果配置延迟暴露。Thread thread = new Thread(new Runnable() {public void run() {try {Thread.sleep(delay);} catch (Throwable e) {}doExport();}});thread.setDaemon(true);thread.setName("DelayExportServiceThread");thread.start();} else {doExport();}}

很尴尬,好多源码都没有相关的文档注释,如果没参考相关资料,第一次阅读会很难受
protected synchronized void doExport() 这个方法的作用大概是执行具体的服务暴露

 protected synchronized void doExport() {//......异常处理省略.......////检查provider是否有配置,如果该属性为null,则new一个ProviderConfig对象,方法内部调用appendProperties(provider)方法,该方法内部会拼装一个 dubbo.tagName.属性名的key,在配置文件中查找值,如果有值则调用属性的setter方法,设置属性值。checkDefault();//如果ProviderConfig实例不为null的情况下,初始化一些配置,通常情况下很少会配置<dubbo:provider/>标签//提供方的缺省值,当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值,可选。if (provider != null) {//application 对应 <dubbo:application/> 应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。if (application == null) {application = provider.getApplication();}// module 对应<dubbo:module/>模块配置,用于配置当前模块信息,可选。if (module == null) {module = provider.getModule();}//registries 对应 <dubbo:registry/> 注册中心配置,用于配置连接注册中心相关信息。if (registries == null) {registries = provider.getRegistries();}// monitor 对应 <dubbo:monitor/> 监控中心配置,用于配置连接监控中心相关信息,可选。if (monitor == null) {monitor = provider.getMonitor();}//protocols 对应 <dubbo:protocol/> 协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。if (protocols == null) {protocols = provider.getProtocols();}}//........省略进一步初始化相关配置的代码行 .......//if (ref instanceof GenericService) { //判断是否是泛化调用接口,这里不重要。interfaceClass = GenericService.class;if (StringUtils.isEmpty(generic)) {generic = Boolean.TRUE.toString();}} else {try {interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}//检查接口类中是否存在指定的方法,如果dubbo:service->dubbo:method 没有配置的情况下,methods为null,该方法不会执行方法校验。如果有相关的配置,该方法会检查name属性对应的方法是否存在,不存在会抛IllegalStateException异常。checkInterfaceAndMethods(interfaceClass, methods);// 检查服务实现,如果ref不是服务的接口实现,则会抛出IllegalStateException异常。checkRef();// 声明该接口非泛化调用generic = Boolean.FALSE.toString();}if(local !=null){ //如果是本地服务,常用配置方案下,local 值为nullif(local=="true"){local=interfaceName+"Local";}Class<?> localClass;try {localClass = ClassHelper.forNameWithThreadContextClassLoader(local);} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}if(!interfaceClass.isAssignableFrom(localClass)){throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);}}if(stub !=null){//如果是远程服务,常用配置方案下,stub值为nullif(stub=="true"){stub=interfaceName+"Stub";}Class<?> stubClass;try {stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}if(!interfaceClass.isAssignableFrom(stubClass)){throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);}}//检查应用配置,如果没有配置会创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。检查失败会抛出IllegalStateException异常checkApplication();//检查注册中心配置,如果没有配置,则会检查配置文件中的dubbo.registry.address 设置,并创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。检查失败会抛出IllegalStateException异常checkRegistry();//检查协议配置,该方法不会抛出异常,如果没配置dubbo:protocol,会取dubbo:provider相关配置填充,如果依旧protocols依旧为null,则创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。checkProtocol();//填充属性 不多说appendProperties(this);//checkStubAndMock(interfaceClass);// 初始化path属性if (path == null || path.length() == 0) {path = interfaceName;}// 暴露服务的URLdoExportUrls();}

doExportUrls(); 实现如下(吐槽,怀疑下到假的源码了,到处都没有文档注释):

 @SuppressWarnings({ "unchecked", "rawtypes" })private void doExportUrls() {//获得注册中心列表List<URL> registryURLs = loadRegistries(true);for (ProtocolConfig protocolConfig : protocols) {doExportUrlsFor1Protocol(protocolConfig, registryURLs);}}

protected List loadRegistries(boolean provider) 类相关解析

protected List<URL> loadRegistries(boolean provider) {// 检查注册中心checkRegistry();List<URL> registryList = new ArrayList<URL>();if (registries != null && registries.size() > 0) {for (RegistryConfig config : registries) {//一般这个拿到的是注册中心地址,例如:zookeeper://1.6.5.5:2181String address = config.getAddress();if (address == null || address.length() == 0) {address = Constants.ANYHOST_VALUE;}String sysaddress = System.getProperty("dubbo.registry.address");if (sysaddress != null && sysaddress.length() > 0) {address = sysaddress;}if (address != null && address.length() > 0 && ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {Map<String, String> map = new HashMap<String, String>();appendParameters(map, application);appendParameters(map, config);map.put("path", RegistryService.class.getName());map.put("dubbo", Version.getVersion());map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));}if (! map.containsKey("protocol")) {//查询注册中心是否支持remote协议if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {map.put("protocol", "remote");} else {map.put("protocol", "dubbo");}}//目前到这里是zookeeper://ip地址:2181/com.alibaba.dubbo.registry.RegistryService?application=配置应用名&dubbo=2.8.4&file=缓存文件路径&logger=log4j&organization=22222&owner=weiythi&pid=20772&timestamp=1510040471140List<URL> urls = UrlUtils.parseURLs(address, map);for (URL url : urls) {url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());//这个url会被转换成这种格式 registry://ip:2181/com.alibaba.dubbo.registry.RegistryService?application=配置的应用名&dubbo=2.8.4&file=缓存文件路径&logger=log4j&organization=22222&owner=weiythi&pid=20772&registry=zookeeper&timestamp=1510040471140 即会将注册中心转换成registry参数放到url中,并将url的protocol设置成registryurl = url.setProtocol(Constants.REGISTRY_PROTOCOL);if ((provider && url.getParameter(Constants.REGISTER_KEY, true))|| (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {registryList.add(url);}}}}}return registryList;}

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {//获得协议信息,默认dubboString name = protocolConfig.getName();if (name == null || name.length() == 0) {name = "dubbo";}String host = protocolConfig.getHost();if (provider != null && (host == null || host.length() == 0)) {host = provider.getHost();}boolean anyhost = false;//判断host是不是为null,或localhost,等本地ip地址if (NetUtils.isInvalidLocalHost(host)) {//......省略 使用各种方式获取本机的实际ip地址........//host = InetAddress.getLocalHost().getHostAddress();//......省略 使用各种方式获取本机的实际ip地址........//}Integer port = protocolConfig.getPort();//.....省略,如果port为null,则尝试使用默认的port,如果默认的port无法使用,则会尝试使用随机的端口Map<String, String> map = new HashMap<String, String>();if (anyhost) {map.put(Constants.ANYHOST_KEY, "true");}map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));}// appendParameters 其实就是把属性值转到map中,这里不做说明appendParameters(map, application);appendParameters(map, module);appendParameters(map, provider, Constants.DEFAULT_KEY);appendParameters(map, protocolConfig);appendParameters(map, this);// 常用配置方案下,methods为null,上文代码注释中有说明if (methods != null && methods.size() > 0) {//......省略,当有暴露指定方法的配置时,会暴露指定方法}if (ProtocolUtils.isGeneric(generic)) {map.put("generic", generic);map.put("methods", Constants.ANY_VALUE);} else {String revision = Version.getVersion(interfaceClass, version);if (revision != null && revision.length() > 0) {map.put("revision", revision);}//获取接口中所有的方法名String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();if(methods.length == 0) {logger.warn("NO method found in service interface " + interfaceClass.getName());map.put("methods", Constants.ANY_VALUE);}else {//拼接方法名为 a,b,c,d 这种形式map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));}}if (! ConfigUtils.isEmpty(token)) { //处理tokenif (ConfigUtils.isDefault(token)) {map.put("token", UUID.randomUUID().toString());} else {map.put("token", token);}}if ("injvm".equals(protocolConfig.getName())) {protocolConfig.setRegister(false);map.put("notify", "false");}// 导出服务String contextPath = protocolConfig.getContextpath();if ((contextPath == null || contextPath.length() == 0) && provider != null) {contextPath = provider.getContextpath();}// 注意这里的URL是com.alibaba.dubbo.common.URL 对象,最后会被处理成一个类似这样的格式,以dubbo协议为例//dubbo://ip:port/接口名?其他信息URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) {url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url);}String scope = url.getParameter(Constants.SCOPE_KEY);//配置为none不暴露if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {exportLocal(url);}//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){if (logger.isInfoEnabled()) {logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}if (registryURLs != null && registryURLs.size() > 0&& url.getParameter("register", true)) {for (URL registryURL : registryURLs) {//遍历注册中心url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));URL monitorUrl = loadMonitor(registryURL);if (monitorUrl != null) {url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());}if (logger.isInfoEnabled()) {logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);}//ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);}} else {Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);}}}this.urls.add(url);}

下一篇集中处理如下代码流程,这一篇只是初步了解了dubbo 服务暴露预处理的一些逻辑,
至于为什么dubbo会选择生成 registry:// 和 dubbo:// 如下两种链接。以后篇幅讨论.
后续将继续跟踪以下代码片段的实现。

 //ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);

Dubbo/Dubbox的服务暴露(一)相关推荐

  1. spring整合dubbo和springboot整合dubbo,实现服务暴露区别

    spring整合dubbo的时候实现服务暴露是这么做的,在xml里配置 那么springboot整合dubbo的时候,是通过dubbo的@Service 注解实现的 之前我们是通过@Autowired ...

  2. 深入解析 Dubbo 3.0 服务端暴露全流程

    简介:随着云原生时代的到来,Dubbo 3.0 的一个很重要的目标就是全面拥抱云原生.正因如此,Dubbo 3.0 为了能够更好的适配云原生,将原来的接口级服务发现机制演进为应用级服务发现机制. 作者 ...

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

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

  4. Dubbo服务暴露原理

    服务暴露原理 配置文件 IOC容器启动,加载配置文件的时候 Dubbo标签处理器,解析每一个标签 封装成对应的组件 service 解析service标签 将service标签信息,封装成Servic ...

  5. 面试官问我:解释一下Dubbo服务暴露

    今天我们要分析的就是Dubbo的服务暴露过程,这个过程属于Dubbo的核心过程之一了,因为Dubbo的大体流程就是服务暴露->服务引用->服务消费这几个主流程,当然还会涉及到注册发现.负载 ...

  6. Dubbo服务暴露的流程

    在Dubbo服务暴露中,需要被暴露的服务的接口类首先会通过proxyFactory代理工厂获得代理的对象invoker,而暴露之后的服务被调用,也将都会通过这个invoker来调用相关的类. 在dub ...

  7. Dubbo服务暴露(导出)流程

    Dubbo作为一个Rpc框架,服务端必须得将自己暴露出去,以便客户端的调用,所以我们来看一下dubbo是如何将服务进行暴露的. 首先我们知道,启动dubbo得进行一些配置,如下图所示的一些dubbo标 ...

  8. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  9. dubbo服务暴露流程总结

    这篇文章主要总结一下dubbo服务端启动的时候服务暴露过程,虽然官方网站和各种博客上已经有很多介绍服务暴露的帖子,但还是想把自己跟源码过程中遇到的问题和心得记录下来,算是个总结,并且本篇文章是基于du ...

最新文章

  1. 如何组织成功的bug bash--摘录
  2. GitHub重磅宣布:今天起,免费私有仓库不限人数,付费功能打5折!
  3. 《Java程序员,上班那点事儿》序——刘博
  4. puppet最新源码包安装学习笔记
  5. 【Linux】一步一步学Linux——ls命令(29)
  6. MATLAB中求矩阵非零元的坐标
  7. 前端学习(1969)vue之电商管理系统电商系统之渲染动态参数和静态参数的表格
  8. redis 中一个字段 修改map_CTO 指名点姓让我带头冲锋,熬了一个通宵,终于把Redis中7千万个Key删完了...
  9. 谷歌官方推出 TensorFlow 中文视频:机器学习从零到一
  10. linux释放内存后设备起不来,Linux-Memory小记
  11. Copy-On-Write容器之一:CopyOnWriteArrayList
  12. 腾讯 2016 春季实习校招 HR 面回忆(C++后台)
  13. 《Genesis-3D游戏引擎系列教程-进阶篇》10:动画融合
  14. docker-compose 学习:通过 Dockerfile 和 build 指令搭建 LNMP
  15. 教你Java 代码性能优化小妙招,速速来看
  16. OAuth2四种不同的标准模式
  17. 新的开始,fighting
  18. Solr学习 DIH增量、定时导入并检索数据
  19. 领域驱动设计(DDD):分层架构的三种模式
  20. 教你把heic格式转化jpg

热门文章

  1. php path separator,php_DIRECTORY_SEPARATOR 和 PATH_SEPARATOR
  2. PLGA-PEG-MAL +多肽A+多肽B+DOX;PLGA-PEG-MAL +DOX
  3. 迷宫寻宝(自编简单版)
  4. 00无人机简介以及课程介绍2020-07-03
  5. android筛选功能代码,Android中 TeaScreenPopupWindow多类型筛选弹框功能的实例代码
  6. 查看从机状态 mysql_mysql常用操作命令
  7. Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十)
  8. 程序化广告(三)- 程序化广告生态链介绍如DSP/ADX/DMP等
  9. 汉明码(计算机组成原理)没听课也能懂
  10. 嵌入式系统词汇表(Embedded System Vocabulary List)