【Dubbo源码二:Dubbo服务导出】

入口

Dubbo服务导出的入口:
image.png
image.png
服务导出是在DubboBootstrapApplicationListener在监听到ApplicationContextEvent的ContextRefreshedEvent事件后,会触发dubboBootstrap.start(), 在这个方法中最后会导出Dubbo服务

DubboBootstrapApplicationListener

DubboBootstrapApplicationListener是哪里注册进来的那?

  1. 入口一:在扫描@DubboService的ServiceClassPostProcessor 里面会注册一个Dubbo的监听器

image.png

  1. 入口二:在解析@DubboReference的注解的时候,调用registerCommonBeans的时候,向Spring容器注册了一个类DubboApplicationListenerRegistrar

image.png

DubboBootstrap.exportServices

image.png
configManager就是一个配置缓存类,里面放的都是之前的配置
image.png

  1. configManager.getServices() 获取的是service,将所有的service转换成serviceBean
  2. 调用serviceBean.export():serviceBean继承的是serviceConfig,最终会调用到serviceConfig.export

ServiceConfig.export

image.png

  1. 判断bootstrap是否初始化,如果没有初始化,需要初始化
  2. 检查并更新服务的参数:checkAndUpdateSubConfigs�
 private void checkAndUpdateSubConfigs() {// Use default configs defined explicitly with global scopecompleteCompoundConfigs();checkDefault();checkProtocol();// init some null configuration.List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class).getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);configInitializers.forEach(e -> e.initServiceConfig(this));// if protocol is not injvm checkRegistryif (!isOnlyInJvm()) {checkRegistry();}this.refresh();if (StringUtils.isEmpty(interfaceName)) {throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");}// 检测 ref 是否为泛化服务类型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);}// 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查checkInterfaceAndMethods(interfaceClass, getMethods());checkRef();generic = Boolean.FALSE.toString();}// local 和 stub 在功能应该是一致的,用于配置本地存根if (local != null) {if ("true".equals(local)) { // 如果配置的事true,那么默认类名就是 接口名拼接Locallocal = interfaceName + "Local";}Class<?> localClass;try {// 获取本地存根类localClass = ClassUtils.forNameWithThreadContextClassLoader(local);} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}if (!interfaceClass.isAssignableFrom(localClass)) {throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);}}// 逻辑同上if (stub != null) {if ("true".equals(stub)) {stub = interfaceName + "Stub";}Class<?> stubClass;try {stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);} catch (ClassNotFoundException e) {throw new IllegalStateException(e.getMessage(), e);}if (!interfaceClass.isAssignableFrom(stubClass)) {throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);}}checkStubAndLocal(interfaceClass);ConfigValidationUtils.checkMock(interfaceClass, this);ConfigValidationUtils.validateServiceConfig(this);postProcessConfig();}
  1. 检查ServiceConfig的配置,如果ServiceConfig中的某些属性如果是空的,那么就从AbstractInterfaceConfig、ModuleConfig、ApplicationConfig、ProviderConfig中获取并赋值给ServiceConfig对象中对应的属性。

  2. 检查provider属性值是否为空,如果为空的话,创建一个新的ProviderConfig并赋值给他。

  3. 检查协议,如果没有单独的配置protocols,则从provider获取配置的协议并赋值给ServiceConfig对应的属性。

  4. 如果配置中心的全局配置或应用配置中也配置了一个协议,那么就会被添加到ServiceConfig中。

  5. 如果protocol协议配置的不是只有injvm协议,那么就需要把服务注册到注册中心里去。

  6. 检查注册中心的配置,如果没有配置的话,从application对象里获取

  7. 刷新ServiceConfig配置

  8. 检查当前服务是否为泛化服务

  9. 检查Stub、Local、Mock

  10. 检验ServiceConfig配置的值是否合法,长度、非法字符等

  11. 通过SPI调用ConfigPostProcessor实现类,进行配置的后置处理

  12. 完善serviceMetadata的参数:version、group、defaultGroup、serviceInterfaceName、serviceType、target等属性值

  13. 检查是否应该被导出。

  14. 如果是延迟暴露,则使用线程池任务调用doExport方法导出。

  15. 如果是正常导出,直接调用doExport方法。

  16. 发送导出完成事件。

ServiceConfig.doExport

image.png

ServiceConfig.doExportUrls

image.png
讲讲这里做了几件事:

  1. 把接口、具体的实现类、ServiceBean、ServiceDescriptor注册到本地仓库的provider

image.png

  1. 查询所有的注册中心,构造出注册中心URL

image.png

  1. 遍历所有协议,将每个协议都进行服务导出 doExportUrlFor1Protocol
 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {String name = protocolConfig.getName();if (StringUtils.isEmpty(name)) {// 如果协议名为空,或空串,则将协议名变量设置为 dubboname = DUBBO;}Map<String, String> map = new HashMap<String, String>();// 添加 side、版本、时间戳以及进程号等信息到 map 中map.put(SIDE_KEY, PROVIDER_SIDE);ServiceConfig.appendRuntimeParameters(map);AbstractConfig.appendParameters(map, getMetrics());// 通过反射将对象的字段信息添加到 map 中AbstractConfig.appendParameters(map, getApplication());AbstractConfig.appendParameters(map, getModule());// remove 'default.' prefix for configs from ProviderConfig// appendParameters(map, provider, Constants.DEFAULT_KEY);AbstractConfig.appendParameters(map, provider);AbstractConfig.appendParameters(map, protocolConfig);AbstractConfig.appendParameters(map, this);MetadataReportConfig metadataReportConfig = getMetadataReportConfig();if (metadataReportConfig != null && metadataReportConfig.isValid()) {map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);}if (CollectionUtils.isNotEmpty(getMethods())) {// methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息for (MethodConfig method : getMethods()) {// 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。// 比如存储 <dubbo:method name="sayHello" retries="2"> 对应的 MethodConfig,// 键 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}AbstractConfig.appendParameters(map, method, method.getName());String retryKey = method.getName() + ".retry";if (map.containsKey(retryKey)) {String retryValue = map.remove(retryKey);// 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0if ("false".equals(retryValue)) {map.put(method.getName() + ".retries", "0");}}// 获取 ArgumentConfig 列表List<ArgumentConfig> arguments = method.getArguments();if (CollectionUtils.isNotEmpty(arguments)) {for (ArgumentConfig argument : arguments) {// convert argument type// 分支1  检测 type 属性是否为空,或者空串if (argument.getType() != null && argument.getType().length() > 0) {Method[] methods = interfaceClass.getMethods();// visit all methodsif (methods.length > 0) {for (int i = 0; i < methods.length; i++) {String methodName = methods[i].getName();// target the method, and get its signature// 1、比对方法名,查找目标方法 2、通过反射获取目标方法的参数类型数组 argtypesif (methodName.equals(method.getName())) {Class<?>[] argtypes = methods[i].getParameterTypes();// one callback in the methodif (argument.getIndex() != -1) {// 检测 ArgumentConfig 中的 type 属性与方法参数列表// 分支2   中的参数名称是否一致,不一致则抛出异常//1. 从 argtypes 数组中获取下标 index 处的元素 argType//2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致//3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {// 添加 ArgumentConfig 字段信息到 map 中,// 键前缀 = 方法名.index,比如:// map = {"sayHello.3": true}AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());} else {throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());}} else { // 分支3 ⭐️//1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数//2. 添加 ArgumentConfig 字段信息到 map 中// multiple callbacks in the methodfor (int j = 0; j < argtypes.length; j++) {Class<?> argclazz = argtypes[j];// 从参数类型列表中查找类型名称为 argument.type 的参数if (argclazz.getName().equals(argument.getType())) {AbstractConfig.appendParameters(map, argument, method.getName() + "." + j);if (argument.getIndex() != -1 && argument.getIndex() != j) {throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());}}}}}}}} else if (argument.getIndex() != -1) {// 用户未配置 type 属性,但配置了 index 属性,且 index != -1// 分支4 ⭐️// 添加 ArgumentConfig 字段信息到 map 中AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());} else {throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");}}}} // end of methods for}// 检测 generic 是否为 "true",并根据检测结果向 map 中添加不同的信息if (ProtocolUtils.isGeneric(generic)) {map.put(GENERIC_KEY, generic);map.put(METHODS_KEY, ANY_VALUE);} else {String revision = Version.getVersion(interfaceClass, version);if (revision != null && revision.length() > 0) {map.put(REVISION_KEY, revision);}/*** 为接口生成包裹类 Wrapper ,Wrapper就是interface的一个动态代理,类似于 mybatis的mapper也会生成一个* 只不过Dubbo用的是自己的动态代理,mybatis用的是JDK动态代理*/String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();if (methods.length == 0) {// 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroylogger.warn("No method found in service interface " + interfaceClass.getName());map.put(METHODS_KEY, ANY_VALUE);} else {// 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中// ->   haveNoReturn,setTestgaga,getTestddd,hellomap.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));}}/*** Here the token value configured by the provider is used to assign the value to ServiceConfig#token*/if (ConfigUtils.isEmpty(token) && provider != null) {token = provider.getToken();}// 添加 token 到 map 中if (!ConfigUtils.isEmpty(token)) {if (ConfigUtils.isDefault(token)) {// 随机生成 tokenmap.put(TOKEN_KEY, UUID.randomUUID().toString());} else {map.put(TOKEN_KEY, token);}}//init serviceMetadata attachmentsserviceMetadata.getAttachments().putAll(map);/*** List<URL> registryURLs* 单个 URL -> registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider*              &dubbo=2.0.2&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=10000&qos.port=22222*              &registry=zookeeper&timestamp=1611892414279* 由此分析呢:协议的路径 就是执行动作的类*/// export IP 获取 本地主机的IP,也就是要暴露接口服务所属主机的IP地址(就是netty或者tomcat绑定的 IP )String host = findConfigedHosts(protocolConfig, registryURLs, map);// export port 获取 暴露的端口,也就是要暴露接口服务 所属 服务的 端口(就是netty或者tomcat绑定的 端口),默认20880Integer port = findConfigedPorts(protocolConfig, name, map);// 组装 URL// getContextPath(protocolConfig) 获取的就是 url的path// 成员变量path 就是 接口名 -> org.apache.dubbo.demo.GreetingService// 所以最终组装的path就是 contextPath+"/"+path ,但是这里contextPath为空// url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider//            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting//            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote//            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0//            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);/*** 前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程*/// You can customize Configurator to append extra parametersif (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) { // 没有自定义的话,就不会进这个判断// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 urlurl = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol()).getConfigurator(url).configure(url);}String scope = url.getParameter(SCOPE_KEY);// don't export when none is configured// scope必须不是 none,如果 scope = none,则什么都不做,if (!SCOPE_NONE.equalsIgnoreCase(scope)) {// export to local if the config is not remote (export to remote only when config is remote)if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {// scope != remote,导出到本地/***  scope = none,不导出服务*  scope != remote,导出到本地*  scope != local,导出到远程* 不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker* Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker* Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,* 可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。*/exportLocal(url);}// export to remote if the config is not local (export to local only when config is local)if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {  // scope != local,导出到远程if (CollectionUtils.isNotEmpty(registryURLs)) {for (URL registryURL : registryURLs) {/*** List<URL> registryURLs* 单个 URL -> registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider*              &dubbo=2.0.2&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=10000&qos.port=22222*              &registry=zookeeper&timestamp=1611892414279* 由此分析呢:协议的路径 就是执行动作的类*///if protocol is only injvm ,not registerif (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {continue;}url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));// 加载监视器链接,一般为空URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);if (monitorUrl != null) {// 将监视器链接作为参数添加到 url 中url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());}if (logger.isInfoEnabled()) {if (url.getParameter(REGISTER_KEY, true)) {logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);} else {logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}}// url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider//            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting//            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote//            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0//            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0// For providers, this is used to enable custom proxy to generate invoker// 看上面英文注释!!,如果自定义代理工厂的话,才会有这个keyString proxy = url.getParameter(PROXY_KEY);if (StringUtils.isNotEmpty(proxy)) {registryURL = registryURL.addParameter(PROXY_KEY, proxy);}/***  为服务提供类(ref)生成 Invoker*  Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory*  ref   -> GreetingServiceImpl    接口实现类*  interfaceClass -> GreetingService  接口*  registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()) -> :*              EXPORT_KEY -> "export"*              url.toFullString() -> 就是url的全路径:dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true......*                                    这个url全路径包含了服务的IP地址,端口,接口名,方法名数组等等*  registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()) 目的就是把需要暴露的服务 encode成一个value*  然后作为一个export参数,拼接到registryURL后面,形如:*  registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?.....&export=经过encode的完整服务*  与上面exportLocal(...)对比着看*/Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfigDelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);/*** 导出服务,并生成 Exporter* 导出服务到本地相比,导出服务到远程的过程要复杂不少,* 其包含了服务导出与服务注册两个过程* PROTOCOL.export(wrapperInvoker) 这行代码呢,根据dubbo-spi机制,会先 url= wrapperInvoker.getUrl(),* 然后 Protocol p = url.getProtocol(),根据duboo-spi的 wrapper机制* p = ProtocolFilterWrapper(ProtocolListenerWrapper(RegistryProtocol))*/Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);exporters.add(exporter);}} else { // 不存在注册中心,仅导出服务// url = dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider//            &bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting//            &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote//            &methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=1144&qos.port=22222&release=&revision=1.0.0//            &side=provider&timeout=5000&timestamp=1611924667311&version=1.0.0// For providers, this is used to enable custom proxy to generate invokerif (logger.isInfoEnabled()) {logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);// 此时:根据dubbo-spi机制,会进入  DubboProtocol的export()方法Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);exporters.add(exporter);}MetadataUtils.publishServiceDefinition(url);}}this.urls.add(url);}
  1. 获取协议名称,如果为空,设置为dubbo协议。
  2. 设置side为provider。
  3. 设置运行时参数,dubbo版本、时间戳、协议版本等。
  4. 将监控、应用、模块、提供者、协议、服务本身、元数据、接口方法、方法参数等信息放入map中。
  5. 如果是泛化服务,设置泛化服务相关信息。
  6. 根据服务接口找到对应的Wrapper类,拿到Wrapper类中所有的方法名字,放入map。
  7. 获取token配置,放入map,token可以在一定程度上防止人为调用dubbo服务。
  8. 将map中的信息放入服务元数据的attachments中。
  9. 获取host、port并构造URL。
  10. 通过DubboSPI获取ConfiguratorFactory的实现类,该步骤可以可以对URL的内容进行更改或做一些定制化操作。
  11. 根据scope来判断是本地注册还是注册到注册中心,如果是none则不进行导出,如果是local代表导出到本地,仅供本地JVM调用,但是也是会走完整的dubbo流程的。
  12. 将服务的元数据信息放、到元数据中心。
  13. 通过代理工厂生成Invoker,并进行服务导出!!! PROTOCAL.export()。

PROTOCOL.export(wrapperInvoker)

PROTOCOL是一个自适应扩展对象,之前讲过自适应扩展类的方法需要有URL参数或者参数的类里有getURL方法,那么wrapperInvoker里面的URL长什么样呢?由于太长了,省略一部分:
image.png

这个URL的协议头为register, 所以实际调用的方法为RegistryProtocol#export

 public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {/*** 包含了 服务导出 与 服务注册 两个过程*//*** 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:* zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2*         &export=dubbo%3A%2F%2F192.168.1.103%3A20880%2Forg.apache.dubbo.demo.GreetingService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.1.103%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dgreeting%26interface%3Dorg.apache.dubbo.demo.GreetingService%26mapping-type%3Dmetadata%26mapping.type%3Dmetadata%26metadata-type%3Dremote%26methods%3DhaveNoReturn%2CsetTestgaga%2CgetTestddd%2Chello%26pid%3D2188%26qos.port%3D22222%26release%3D%26revision%3D1.0.0%26side%3Dprovider%26timeout%3D5000%26timestamp%3D1611975477170%26version%3D1.0.0&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=2188&qos.port=22222&timestamp=1611975477162* export解码后:* export=dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0&id=registry1&mapping-type=metadata&mapping.type=metadata&pid=2188&qos.port=22222&timestamp=1611975477162*/URL registryUrl = getRegistryUrl(originInvoker);/*** url to export locally 获取已注册的服务提供者 URL,其实就是上面的url的export的值,比如:* dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0*/URL providerUrl = getProviderUrl(originInvoker);// Subscribe the override data// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call//  the same service. Because the subscribed is cached key with the name of the service, it causes the//  subscription information to cover./*** 获取订阅 URL,比如:* provider://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&bind.ip=192.168.1.103&bind.port=20880*              &category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&qos.port=22222&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0*/final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);// 创建监听器final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);/*** export invoker  导出服务*/final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);// url to registry// 根据 URL 加载 Registry 实现类,里面有很多spi机制使用,最终得到 :比如 ZookeeperRegistry// 但是 外面加了一层wrapper,所以最终 registry =  ListenerRegistryWrapper// 同时会注册监听事件!!!!!final Registry registry = getRegistry(originInvoker);// dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=haveNoReturn,setTestgaga,getTestddd,hello&pid=2188&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1611975477170&version=1.0.0final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);// decide if we need to delay publish   获取 register 参数boolean register = providerUrl.getParameter(REGISTER_KEY, true);if (register) {// 向注册中心注册服务/*** 服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。* 但通常我们不会这么做,直连方式不利于服务治理.*/register(registryUrl, registeredProviderUrl);}// register stated url on provider modelregisterStatedUrl(registryUrl, registeredProviderUrl, register);exporter.setRegisterUrl(registeredProviderUrl);exporter.setSubscribeUrl(overrideSubscribeUrl);// Deprecated! Subscribe to override rules in 2.6.x or before.// 向注册中心进行订阅 override 数据.registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);notifyExport(exporter);//Ensure that a new exporter instance is returned every time export// 创建并返回 DestroyableExporterreturn new DestroyableExporter<>(exporter);}

具体做了这几件事:

  1. 通过registryUrl(zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2) + providerUrl( dubbo://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?any)生成 overrideSubscribeUrl(provider://192.168.1.103:20880/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider)在providerUrl的基础上增加参数category=configurators&check=false,根据providerUrl、registryUrl对参数进行简化,因为有些参数是没用的,没有必要放到注册中心,比如:monitor、bind.ip、bind.port等,简化后生成要注册的URL,并注册到注册中心
  2. 进行本地导出:doLocalExport

image.png
由于这时候运行的协议为Dubbo,所以回调用到DubboProtocol,这里会启动Tomcat或者Netty等容器
image.png

  1. 根据url来获取服务key,一般为接口类名
  2. 构造一个DubboExporter,并放入到缓存中
  3. 开启Netty服务:openServer

image.png
image.png

  1. 根据传进来的URL生成服务URL,该URL比原来新增加了channel.readonly.set=TRUE、heartbeat=6000、codec=dubbo参数2. 从Url中获取协议的服务器端实现类型,比如:dubbo协议的mina、netty等;http协议的jetty、tomcat等,**默认为netty协议**3. 把创建好的Server进行返回,在创建Server的时候,最终**会调用到NettyServer.doOpen**

image.png
设置netty的参数,并启动netty

  1. 根据Invoker 中配置的optimizer 参数获取拓展的自定义序列号处理类

  2. register(registerUrl, registeredProviderUrl)

image.png

  1. 通过Url来获取注册中心实例 ZookeeperRegistry
  2. 调用register进行注册中心注册,最终执行到FailbackRegistry.registry, 这个方法最终调用到zkClient.create将URL注册到注册中心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2778625.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Java异常处理 throw和throws

目录 throwthrows实例制造异常 在Java中&#xff0c;throw和throws关键字都与异常处理有关&#xff0c;但它们的使用方式和目的有所不同。 throw throw关键字&#xff1a; * throw用于在代码中显式地抛出一个异常。你可以使用它来触发一个异常&#xff0c;并指定异常的类型。…

python接口自动化---接口测试报告模板(详解)

简介 接口测试报告是软件测试过程中非常重要的一部分&#xff0c;通过接口测试报告我们可以了解系统在接口层面上的稳定性和可靠性。下面是一个简单的接口测试报告模板&#xff1a; 测试概述 在这个部分中&#xff0c;您需要简要阐述接口测试的目的和范围。测试环境 在这个部…

网络的基本概念和socket编程

网络的基本概念 1.协议1.1 协议的基本概念1.2 常见的协议 2.分层模型2.1网络七层OSI 7层模型&#xff1a;物数网传会表应(口诀)2.2TCP/IP模型2.3数据通信的过程2.4网络的设计模式2.5以太网帧的格式 3.SOCKET编程3.1网络字节序3.2 相关结构体和函数3.3 代码实现 1.协议 1.1 协议…

NAS如何成为生产力?使用绿联DX4600 Pro搭建图床并实现创作自由

NAS如何成为生产力&#xff1f;使用绿联DX4600 Pro搭建图床并实现创作自由 哈喽小伙伴们好&#xff0c;我是Stark-C~ 关注我的小伙伴都知道&#xff0c;我之前有分享过我的创作过程与工具&#xff0c;其中介绍了我个人其实一直都是使用Markdown的编辑器来进行图文创作的。 我…

【数学建模】【2024年】【第40届】【MCM/ICM】【B题 搜寻潜水器】【解题思路】

一、题目 &#xff08;一&#xff09;赛题原文 2024 MCM Problem A: Resource Availability and Sex Ratios Maritime Cruises Mini-Submarines (MCMS), a company based in Greece, builds submersibles capable of carrying humans to the deepest parts of the ocean. A …

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Web组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Web组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Web组件 提供具有网页显示能力的Web组件&#xff0c;ohos.web.webview提供web控制能…

《剑指 Offer》专项突破版 - 面试题 38、39 和 40 : 通过三道面试题详解单调栈(C++ 实现)

目录 面试题 38 : 每日温度 面试题 39 : 直方图最大矩形面积 方法一、暴力求解 方法二、递归求解 方法三、单调栈法 面试题 40 : 矩阵中的最大矩形 面试题 38 : 每日温度 题目&#xff1a; 输入一个数组&#xff0c;它的每个数字是某天的温度。请计算每天需要等几天才会…

AJAX——认识URL

1 什么是URL&#xff1f; 统一资源定位符&#xff08;英语&#xff1a;Uniform Resource Locator&#xff0c;缩写&#xff1a;URL&#xff0c;或称统一资源定位器、定位地址、URL地址&#xff09;俗称网页地址&#xff0c;简称网址&#xff0c;是因特网上标准的资源的地址&…

生于越南,“开源改变了我的人生!”

注&#xff1a;本文精选自《新程序员 007&#xff1a;大模型时代的开发者》&#xff0c;欢迎点击订购。 作者 | 王启隆 责编 | 唐小引 出品 | 《新程序员》编辑部 随着人工智能浪潮的席卷&#xff0c;开源不再仅仅是计算机领域的一个话题&#xff0c;而是成为推动技术创新…

【动态规划】【回文】【字符串】1278分割回文串 III

作者推荐 【动态规划】【前缀和】【C算法】LCP 57. 打地鼠 本文涉及知识点 动态规划汇总 LeetCode1278分割回文串 III 给你一个由小写字母组成的字符串 s&#xff0c;和一个整数 k。 请你按下面的要求分割字符串&#xff1a; 首先&#xff0c;你可以将 s 中的部分字符修改…

【Linux系统 04】OpenEuler配置

目录 一、镜像文件下载 二、配置静态IP 三、启动SSH连接 四、远程免密登录 五、安装常用软件 一、镜像文件下载 官方下载地址&#xff1a;openEuler下载 | 欧拉系统ISO镜像 | openEuler社区官网 选择一个版本&#xff0c;lopenEuler通常有两种版本&#xff1a; 创新版&…

Java 内存区域介绍

&#xff08;1&#xff09;程序计数器 程序计数器主要有两个作用&#xff1a; 字节码解释器通过改变程序计数器来依次读取指令&#xff0c;从而实现代码的流程控制&#xff0c;如&#xff1a;顺序执行、选择、循环、异常处理。 在多线程的情况下&#xff0c;程序计数器用于记录…

C++笔记之regex(正则表达式)

C++笔记之regex(正则表达式) ——2024-02-10 ——《C++标准库》(第2版,侯捷译) Page 717 code review! 文章目录 C++笔记之regex(正则表达式)例1:使用正则表达式进行搜索(`std::regex_search`)例2:使用正则表达式进行全文匹配(`std::regex_match`)例3:使用正则表达式…

Linux操作系统基础(八):Linux的vi/vim编辑器

文章目录 Linux的vi/vim编辑器 一、vi/vim编辑器介绍 二、打开文件 三、VIM编辑器的三种模式(重点) 四、命令模式相关命令 五、底行模式相关命令 Linux的vi/vim编辑器 一、vi/vim编辑器介绍 vi是visual interface的简称, 是Linux中最经典的文本编辑器 vi的核心设计思想…

【九章斩题录】Leetcode:判定是否互为字符重排(C/C++)

面试题 01.02. 判定是否互为字符重排 ✅ 模板&#xff1a;C class Solution { public:bool CheckPermutation(string s1, string s2) {} }; 「 法一 」排序 &#x1f4a1; 思路&#xff1a;看到题目中说 "重新排列后能否变成另一个字符串"&#xff0c;等等……重新…

读千脑智能笔记10_人类智能存在的风险

1. 人类智能存在的风险 1.1. “末日时钟” 1.1.1. 核战争引发的大火列为地球毁灭的主要原因 1.1.2. 气候变化列为人类自我毁灭的第二大潜在原因 1.2. 除非我们刻意加入自私的驱动力、动机或情感&#xff0c;否则智能机器并不会威胁到人类的生存 1.2.1. 人类在不远的将来会…

系统架构21 - 统一建模语言UML(下)

UML图 UML中的图分类作用 视图用例视图逻辑视图进程视图实现视图部署视图 UML中的图 “图”是一组元素的图形表示&#xff0c;大多数情况下把图画成顶点&#xff08;代表事物&#xff09;和弧&#xff08;代表关系&#xff09;的连通图。为了对系统进行可视化&#xff0c;可以…

【经验】PIC16F877A串口发送字符串问题

PIC16F877A串口发送&#xff0c;查询方式&#xff0c;就为了调出这个费了我一天时间&#xff0c;原来是串口芯片电压问题&#xff0c;现总结如下&#xff1a; 1、注意232串口芯片供电电压&#xff0c;有5V和3.3V的 2、注意TXD、RXD接线&#xff0c;单片机的TXD接232芯片的R2O…

Linux中孤儿/僵尸进程/wait/waitpid函数

孤儿进程&#xff1a; 概念&#xff1a;若子进程的父进程已经死掉&#xff0c;而子进程还存活着&#xff0c;这个进程就成了孤儿进程。 为了保证每个进程都有一个父进程&#xff0c;孤儿进程会被init进程领养&#xff0c;init进程成为了孤儿进程的养父进程&#xff0c;当孤儿…

【计算机网络】时延,丢包,吞吐量(分组交换网络

时延 结点处理时延(nodal processing delay&#xff09; dproc 排队时延&#xff08;queuing delay&#xff09; dqueue 传输时延&#xff08;transmission delay&#xff09; dtrans 路由器将分组推出所需要的时间&#xff0c;是分组长度和链路传输速率的函数 传播时…