视频网站解析源码分享 视频网址解析原理

这篇文章给大家聊聊关于视频网站解析源码分享,以及视频网址解析原理对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。

概述

dubbo是一个简单易用的RPC框架,通过简单的提供者,消费者配置就能完成无感的网络调用。那么在dubbo中是如何将提供者的服务暴露出去,消费者又是如何获取到提供者相关信息的呢?这就是本章我们要讨论的内容。

dubbo与spring的整合

在了解dubbo的服务注册和服务发现之前,我们首先需要掌握一个知识点:Spring中自定义Schema。

Spring自定义Schema

Dubbo现在的设计是完全无侵入,也就是使用者只依赖于配置契约。在Dubbo中,可以使用XML配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring会读取配置文件,生成注入相关Bean。那Dubbo如何实现自定义XML被Spring加载读取呢?

从Spring2.0开始,Spring开始提供了一种基于XMLSchema格式扩展机制,用于定义和配置bean。

入门案例

学习和使用SpringXMLSchema扩展机制并不难,需要下面几个步骤:

创建配置属性的JavaBean对象创建一个XMLSchema文件,描述自定义的合法构建模块,也就是xsd文件。自定义处理器类,并实现NamespaceHandler接口。自定义解析器,实现BeanDefinitionParser接口(最关键的部分)。编写Spring.handlers和Spring.schemas文件配置所有部件

定义JavaBean对象,在spring中此对象会根据配置自动创建

publicclassUser{\nprivateStringid;\nprivateStringname;\nprivateIntegerage;\n//省略gettersetter方法\n}

在META-INF下定义user.xsd文件,使用xsd用于描述标签的规则

<?xmlversion=&34;encoding=&34;?>\n<xsd:schema\nxmlns=&34;\nxmlns:xsd=&34;\nxmlns:beans=&34;\ntargetNamespace=&34;\nelementFormDefault=&34;\nattributeFormDefault=&34;>\n<xsd:importnamespace=&34;/>\n<xsd:elementname=&34;>\n<xsd:complexType>\n<xsd:complexContent>\n<xsd:extensionbase=&34;>\n<xsd:attributename=&34;type=&34;/>\n<xsd:attributename=&34;type=&34;/>\n</xsd:extension>\n</xsd:complexContent>\n</xsd:complexType>\n</xsd:element>\n</xsd:schema>

Spring读取xml文件时,会根据标签的命名空间找到其对应的NamespaceHandler,我们在NamespaceHandler内会注册标签对应的解析器BeanDefinitionParser。

packagecom.itheima.schema;\n\nimportorg.springframework.beans.factory.xml.NamespaceHandlerSupport;\n\npublicclassUserNamespaceHandlerextendsNamespaceHandlerSupport{\npublicvoidinit(){\nregisterBeanDefinitionParser(&34;,newUserBeanDefinitionParser());\n}\n}

BeanDefinitionParser是标签对应的解析器,Spring读取到对应标签时会使用该类进行解析;

publicclassUserBeanDefinitionParserextends\nAbstractSingleBeanDefinitionParser{\n\nprotectedClassgetBeanClass(Elementelement){\nreturnUser.class;\n}\n\nprotectedvoiddoParse(Elementelement,BeanDefinitionBuilderbean){\nStringname=element.getAttribute(&34;);\nStringage=element.getAttribute(&34;);\nStringid=element.getAttribute(&34;);\nif(StringUtils.hasText(id)){\nbean.addPropertyValue(&34;,id);\n}\nif(StringUtils.hasText(name)){\nbean.addPropertyValue(&34;,name);\n}\nif(StringUtils.hasText(age)){\nbean.addPropertyValue(&34;,Integer.valueOf(age));\n}\n}\n}

定义spring.handlers文件,内部保存命名空间与NamespaceHandler类的对应关系;必须放在classpath下的META-INF文件夹中。

http\\://www.itheima.com/schema/user=com.itheima.schema.UserNamespaceHandler

定义spring.schemas文件,内部保存命名空间对应的xsd文件位置;必须放在classpath下的META-INF文件夹中。

http\\://www.itheima.com/schema/user.xsd=META-INF/user.xsd

代码准备好了之后,就可以在spring工程中进行使用和测试,定义spring配置文件,导入对应约束

<?xmlversion=&34;encoding=&34;?>\n<beansxmlns=&34;\nxmlns:xsi=&34;\nxmlns:context=&34;\nxmlns:util=&34;\nxmlns:task=&34;\nxmlns:aop=&34;\nxmlns:tx=&34;\nxmlns:itheima=&34;\nxsi:schemaLocation=&34;>\n\n<itheima:userid=&34;name=&34;age=&34;></itheima:user>\n\n</beans>\n

编写测试类,通过spring容器获取对象user

publicclassSchemaDemo{\npublicstaticvoidmain(String[]args){\nApplicationContextctx=newClassPathXmlApplicationContext(&34;);\nUseruser=(User)ctx.getBean(&34;);\nSystem.out.println(user);\n}\n}

dubbo中的相关对象

Dubbo是运行在spring容器中,dubbo的配置文件也是通过spring的配置文件applicationContext.xml来加载,所以dubbo的自定义配置标签实现,其实同样依赖spring的xmlschema机制

可以看出Dubbo所有的组件都是由DubboBeanDefinitionParser解析,并通过registerBeanDefinitionParser方法来注册到spring中最后解析对应的对象。这些对象中我们重点关注的有以下两个:

ServiceBean:服务提供者暴露服务的核心对象ReferenceBean:服务消费者发现服务的核心对象RegistryConfig:定义注册中心的核心配置对象

服务暴露

前面主要探讨了Dubbo中schema、XML的相关原理,这些内容对理解框架整体至关重要,在此基础上我们继续探讨服务是如何依靠前面的配置进行服务暴露

名词解释

在Dubbo的核心领域模型中:

Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。export:暴露远程服务refer:引用远程服务proxyFactory:获取一个接口的代理类getInvoker:针对server端,将服务对象,如DemoServiceImpl包装成一个Invoker对象getProxy:针对client端,创建接口的代理对象,例如DemoService的接口。Invocation是会话域,它持有调用过程中的变量,比如方法名,参数等

整体流程

在详细探讨服务暴露细节之前,我们先看一下整体duubo的服务暴露原理

在整体上看,Dubbo框架做服务暴露分为两大部分,第一步将持有的服务实例通过代理转换成Invoker,第二步会把Invoker通过具体的协议(比如Dubbo)转换成Exporter,框架做了这层抽象也大大方便了功能扩展。

服务提供方暴露服务的蓝色初始化链,时序图如下:

源码分析

(1)导出入口

服务导出的入口方法是ServiceBean的onApplicationEvent。onApplicationEvent是一个事件响应方法,该方法会在收到Spring上下文刷新事件后执行服务导出操作。方法代码如下:

publicvoidonApplicationEvent(ContextRefreshedEventevent){\n//是否有延迟导出&&是否已导出&&是不是已被取消导出\nif(isDelay()&&!isExported()&&!isUnexported()){\n//导出服务\nexport();\n}\n}

onApplicationEvent方法在经过一些判断后,会决定是否调用export方法导出服务。在export根据配置执行相应的动作。最终进入到doExportUrls导出服务方法

privatevoiddoExportUrls(){\n//加载注册中心链接\nList<URL>registryURLs=loadRegistries(true);\n//遍历protocols,并在每个协议下导出服务\nfor(ProtocolConfigprotocolConfig:protocols){\ndoExportUrlsFor1Protocol(protocolConfig,registryURLs);\n}\n}

关于多协议多注册中心导出服务首先是根据配置,以及其他一些信息组装URL。前面说过,URL是Dubbo配置的载体,通过URL可让Dubbo的各种配置在各个模块之间传递。

privatevoiddoExportUrlsFor1Protocol(ProtocolConfigprotocolConfig,List<URL>registryURLs){\nStringname=protocolConfig.getName();\n//如果协议名为空,或空串,则将协议名变量设置为dubbo\nif(name==null||name.length()==0){\nname=&34;;\n}\n\nMap<String,String>map=newHashMap<String,String>();\n\t\n//略\n\n//获取上下文路径\nStringcontextPath=protocolConfig.getContextpath();\nif((contextPath==null||contextPath.length()==0)&&provider!=null){\ncontextPath=provider.getContextpath();\n}\n\n//获取host和port\nStringhost=this.findConfigedHosts(protocolConfig,registryURLs,map);\nIntegerport=this.findConfigedPorts(protocolConfig,name,map);\n//组装URL\nURLurl=newURL(name,host,port,(contextPath==null||contextPath.length()==0?&34;:contextPath+&34;)+path,map);\n\n//省略无关代码\n}

上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到map中,最后将map和主机名等数据传给URL构造方法创建URL对象。前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地(JVM),和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。如下:

privatevoiddoExportUrlsFor1Protocol(ProtocolConfigprotocolConfig,List<URL>registryURLs){\n\n//省略无关代码\nStringscope=url.getParameter(Constants.SCOPE_KEY);\n//如果scope=none,则什么都不做\nif(!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)){\n//scope!=remote,导出到本地\nif(!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)){\nexportLocal(url);\n}\n//scope!=local,导出到远程\nif(!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)){\nif(registryURLs!=null&&!registryURLs.isEmpty()){\nfor(URLregistryURL:registryURLs){\n\t//省略无关代码\n\n//为服务提供类(ref)生成Invoker\nInvoker<?>invoker=proxyFactory.getInvoker(ref,(Class)interfaceClass,registryURL.addParameterAndEncoded(Constants.EXPORT_KEY,url.toFullString()));\n//DelegateProviderMetaDataInvoker用于持有Invoker和ServiceConfig\nDelegateProviderMetaDataInvokerwrapperInvoker=newDelegateProviderMetaDataInvoker(invoker,this);\n\n//导出服务,并生成Exporter\nExporter<?>exporter=protocol.export(wrapperInvoker);\nexporters.add(exporter);\n}\n\n//不存在注册中心,仅导出服务\n}else{\n\t\t\t\t//略\n}\n}\n}\nthis.urls.add(url);\n}

上面代码根据url中的scope参数决定服务导出方式,分别如下:

scope=none,不导出服务scope!=remote,导出到本地scope!=local,导出到远程

不管是导出到本地,还是远程。进行服务导出之前,均需要先创建Invoker,这是一个很重要的步骤。因此下面先来分析Invoker的创建过程。Invoker是由ProxyFactory创建而来,Dubbo默认的ProxyFactory实现类是JavassistProxyFactory。下面我们到JavassistProxyFactory代码中,探索Invoker的创建过程。如下:

public<T>Invoker<T>getInvoker(Tproxy,Class<T>type,URLurl){\n\t//为目标类创建Wrapper\nfinalWrapperwrapper=Wrapper.getWrapper(proxy.getClass().getName().indexOf(&39;)<0?proxy.getClass():type);\n//创建匿名Invoker类对象,并实现doInvoke方法。\nreturnnewAbstractProxyInvoker<T>(proxy,type,url){\n@Override\nprotectedObjectdoInvoke(Tproxy,StringmethodName,\nClass<?>[]parameterTypes,\nObject[]arguments)throwsThrowable{\n\t\t\t//调用Wrapper的invokeMethod方法,invokeMethod最终会调用目标方法\nreturnwrapper.invokeMethod(proxy,methodName,parameterTypes,arguments);\n}\n};\n}

如上,JavassistProxyFactory创建了一个继承自AbstractProxyInvoker类的匿名对象,并覆写了抽象方法doInvoke。

(2)导出服务到本地

Invoke创建成功之后,接下来我们来看本地导出

privatevoidexportLocal(URLurl){\n//如果URL的协议头等于injvm,说明已经导出到本地了,无需再次导出\nif(!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())){\nURLlocal=URL.valueOf(url.toFullString())\n.setProtocol(Constants.LOCAL_PROTOCOL)//设置协议头为injvm\n.setHost(LOCALHOST)\n.setPort(0);\nServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));\n//创建Invoker,并导出服务,这里的protocol会在运行时调用InjvmProtocol的export方法\nExporter<?>exporter=protocol.export(\nproxyFactory.getInvoker(ref,(Class)interfaceClass,local));\nexporters.add(exporter);\n}\n}

exportLocal方法比较简单,首先根据URL协议头决定是否导出服务。若需导出,则创建一个新的URL并将协议头、主机名以及端口设置成新的值。然后创建Invoker,并调用InjvmProtocol的export方法导出服务。下面我们来看一下InjvmProtocol的export方法都做了哪些事情。

public<T>Exporter<T>export(Invoker<T>invoker)throwsRpcException{\n//创建InjvmExporter\nreturnnewInjvmExporter<T>(invoker,invoker.getUrl().getServiceKey(),exporterMap);\n}

如上,InjvmProtocol的export方法仅创建了一个InjvmExporter,无其他逻辑。到此导出服务到本地就分析完了。

(3)导出服务到远程

接下来,我们继续分析导出服务到远程的过程。导出服务到远程包含了服务导出与服务注册两个过程。先来分析服务导出逻辑。我们把目光移动到RegistryProtocol的export方法上。

public<T>Exporter<T>export(finalInvoker<T>originInvoker)throwsRpcException{\n//导出服务\nfinalExporterChangeableWrapper<T>exporter=doLocalExport(originInvoker);\n\n//获取注册中心URL\nURLregistryUrl=getRegistryUrl(originInvoker);\n\n//根据URL加载Registry实现类,比如ZookeeperRegistry\nfinalRegistryregistry=getRegistry(originInvoker);\n\n//获取已注册的服务提供者URL,比如:\nfinalURLregisteredProviderUrl=getRegisteredProviderUrl(originInvoker);\n\n//获取register参数\nbooleanregister=registeredProviderUrl.getParameter(&34;,true);\n\n//向服务提供者与消费者注册表中注册服务提供者\nProviderConsumerRegTable.registerProvider(originInvoker,registryUrl,registeredProviderUrl);\n\n//根据register的值决定是否注册服务\nif(register){\n//向注册中心注册服务\nregister(registryUrl,registeredProviderUrl);\nProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);\n}\n\n//获取订阅URL,比如:\nfinalURLoverrideSubscribeUrl=getSubscribedOverrideUrl(registeredProviderUrl);\n//创建监听器\nfinalOverrideListeneroverrideSubscribeListener=newOverrideListener(overrideSubscribeUrl,originInvoker);\noverrideListeners.put(overrideSubscribeUrl,overrideSubscribeListener);\n//向注册中心进行订阅override数据\nregistry.subscribe(overrideSubscribeUrl,overrideSubscribeListener);\n//创建并返回DestroyableExporter\nreturnnewDestroyableExporter<T>(exporter,originInvoker,overrideSubscribeUrl,registeredProviderUrl);\n}

上面代码看起来比较复杂,主要做如下一些操作:

调用doLocalExport导出服务向注册中心注册服务向注册中心进行订阅override数据创建并返回DestroyableExporter

下面先来分析doLocalExport方法的逻辑,如下:

private<T>ExporterChangeableWrapper<T>doLocalExport(finalInvoker<T>originInvoker){\nStringkey=getCacheKey(originInvoker);\n//访问缓存\nExporterChangeableWrapper<T>exporter=(ExporterChangeableWrapper<T>)bounds.get(key);\nif(exporter==null){\nsynchronized(bounds){\nexporter=(ExporterChangeableWrapper<T>)bounds.get(key);\nif(exporter==null){\n//创建Invoker为委托类对象\nfinalInvoker<?>invokerDelegete=newInvokerDelegete<T>(originInvoker,getProviderUrl(originInvoker));\n//调用protocol的export方法导出服务\nexporter=newExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete),originInvoker);\n\n//写缓存\nbounds.put(key,exporter);\n}\n}\n}\nreturnexporter;\n}

接下来,我们把重点放在Protocol的export方法上。假设运行时协议为dubbo,此处的protocol变量会在运行时加载DubboProtocol,并调用DubboProtocol的export方法。

public<T>Exporter<T>export(Invoker<T>invoker)throwsRpcException{\nURLurl=invoker.getUrl();\n\n//获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:\n//demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880\nStringkey=serviceKey(url);\n//创建DubboExporter\nDubboExporter<T>exporter=newDubboExporter<T>(invoker,key,exporterMap);\n//将<key,exporter>键值对放入缓存中\nexporterMap.put(key,exporter);\n\n\t//省略无关代码\n\n//启动服务器\nopenServer(url);\n//优化序列化\noptimizeSerialization(url);\nreturnexporter;\n}

(4)开启Netty服务

如上,我们重点关注DubboExporter的创建以及openServer方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。下面分析openServer方法。

privatevoidopenServer(URLurl){\n//获取host:port,并将其作为服务器实例的key,用于标识当前的服务器实例\nStringkey=url.getAddress();\nbooleanisServer=url.getParameter(Constants.IS_SERVER_KEY,true);\nif(isServer){\n//访问缓存\nExchangeServerserver=serverMap.get(key);\nif(server==null){\n//创建服务器实例\nserverMap.put(key,createServer(url));\n}else{\n//服务器已创建,则根据url中的配置重置服务器\nserver.reset(url);\n}\n}\n}

接下来分析服务器实例的创建过程。如下:

privateExchangeServercreateServer(URLurl){\nurl=url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,\n//添加心跳检测配置到url中\nurl=url.addParameterIfAbsent(Constants.HEARTBEAT_KEY,String.valueOf(Constants.DEFAULT_HEARTBEAT));\n\t//获取server参数,默认为netty\nStringstr=url.getParameter(Constants.SERVER_KEY,Constants.DEFAULT_REMOTING_SERVER);\n\n\t//通过SPI检测是否存在server参数所代表的Transporter拓展,不存在则抛出异常\nif(str!=null&&str.length()>0&&!ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))\nthrownewRpcException(&34;+str+&34;+url);\n\n//添加编码解码器参数\nurl=url.addParameter(Constants.CODEC_KEY,DubboCodec.NAME);\nExchangeServerserver;\ntry{\n//创建ExchangeServer\nserver=Exchangers.bind(url,requestHandler);\n}catch(RemotingExceptione){\nthrownewRpcException(&34;);\n}\n\n\t//获取client参数,可指定netty,mina\nstr=url.getParameter(Constants.CLIENT_KEY);\nif(str!=null&&str.length()>0){\n//获取所有的Transporter实现类名称集合,比如supportedTypes=[netty,mina]\nSet<String>supportedTypes=ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();\n//检测当前Dubbo所支持的Transporter实现类名称列表中,\n//是否包含client所表示的Transporter,若不包含,则抛出异常\nif(!supportedTypes.contains(str)){\nthrownewRpcException(&34;);\n}\n}\nreturnserver;\n}

如上,createServer包含三个核心的逻辑。第一是检测是否存在server参数所代表的Transporter拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持client参数所表示的Transporter拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。

publicstaticExchangeServerbind(URLurl,ExchangeHandlerhandler)throwsRemotingException{\nif(url==null){\nthrownewIllegalArgumentException(&34;);\n}\nif(handler==null){\nthrownewIllegalArgumentException(&34;);\n}\nurl=url.addParameterIfAbsent(Constants.CODEC_KEY,&34;);\n//获取Exchanger,默认为HeaderExchanger。\n//紧接着调用HeaderExchanger的bind方法创建ExchangeServer实例\nreturngetExchanger(url).bind(url,handler);\n}

上面代码比较简单,就不多说了。下面看一下HeaderExchanger的bind方法。

publicExchangeServerbind(URLurl,ExchangeHandlerhandler)throwsRemotingException{\n\t//创建HeaderExchangeServer实例,该方法包含了多个逻辑,分别如下:\n\t//1.newHeaderExchangeHandler(handler)\n\t//\t2.newDecodeHandler(newHeaderExchangeHandler(handler))\n\t//3.Transporters.bind(url,newDecodeHandler(newHeaderExchangeHandler(handler)))\nreturnnewHeaderExchangeServer(Transporters.bind(url,newDecodeHandler(newHeaderExchangeHandler(handler))));\n}

HeaderExchanger的bind方法包含的逻辑比较多,但目前我们仅需关心Transporters的bind方法逻辑即可。该方法的代码如下:

publicstaticServerbind(URLurl,ChannelHandler…handlers)throwsRemotingException{\nif(url==null){\nthrownewIllegalArgumentException(&34;);\n}\nif(handlers==null||handlers.length==0){\nthrownewIllegalArgumentException(&34;);\n}\nChannelHandlerhandler;\nif(handlers.length==1){\nhandler=handlers[0];\n}else{\n\t//如果handlers元素数量大于1,则创建ChannelHandler分发器\nhandler=newChannelHandlerDispatcher(handlers);\n}\n//获取自适应Transporter实例,并调用实例方法\nreturngetTransporter().bind(url,handler);\n}

如上,getTransporter()方法获取的Transporter是在运行时动态创建的,类名为TransporterAdaptive,也就是自适应拓展类。TransporterAdaptive会在运行时根据传入的URL参数决定加载什么类型的Transporter,默认为NettyTransporter。调用NettyTransporter.bind(URL,ChannelHandler)方法。创建一个NettyServer实例。调用NettyServer.doOPen()方法,服务器被开启,服务也被暴露出来了。

(5)服务注册

本节内容以Zookeeper注册中心作为分析目标,其他类型注册中心大家可自行分析。下面从服务注册的入口方法开始分析,我们把目光再次移到RegistryProtocol的export方法上。如下:

public<T>Exporter<T>export(finalInvoker<T>originInvoker)throwsRpcException{\n\n//${导出服务}\n\n//省略其他代码\n\nbooleanregister=registeredProviderUrl.getParameter(&34;,true);\nif(register){\n//注册服务\nregister(registryUrl,registeredProviderUrl);\nProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);\n}\n\nfinalURLoverrideSubscribeUrl=getSubscribedOverrideUrl(registeredProviderUrl);\nfinalOverrideListeneroverrideSubscribeListener=newOverrideListener(overrideSubscribeUrl,originInvoker);\noverrideListeners.put(overrideSubscribeUrl,overrideSubscribeListener);\n//订阅override数据\nregistry.subscribe(overrideSubscribeUrl,overrideSubscribeListener);\n\n//省略部分代码\n}

RegistryProtocol的export方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:

publicvoidregister(URLregistryUrl,URLregistedProviderUrl){\n//获取Registry\nRegistryregistry=registryFactory.getRegistry(registryUrl);\n//注册服务\nregistry.register(registedProviderUrl);\n}

register方法包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。接下来分两节内容对这两步操作进行分析。

这里以Zookeeper注册中心为例进行分析。下面先来看一下getRegistry方法的源码,这个方法由AbstractRegistryFactory实现。如下:

publicRegistrygetRegistry(URLurl){\nurl=url.setPath(RegistryService.class.getName())\n.addParameter(Constants.INTERFACE_KEY,RegistryService.class.getName())\n.removeParameters(Constants.EXPORT_KEY,Constants.REFER_KEY);\nStringkey=url.toServiceString();\nLOCK.lock();\ntry{\n\t//访问缓存\nRegistryregistry=REGISTRIES.get(key);\nif(registry!=null){\nreturnregistry;\n}\n\n//缓存未命中,创建Registry实例\nregistry=createRegistry(url);\nif(registry==null){\nthrownewIllegalStateException(&34;);\n}\n\n//写入缓存\nREGISTRIES.put(key,registry);\nreturnregistry;\n}finally{\nLOCK.unlock();\n}\n}\n\nprotectedabstractRegistrycreateRegistry(URLurl);

如上,getRegistry方法先访问缓存,缓存未命中则调用createRegistry创建Registry。在此方法中就是通过newZookeeperRegistry(url,zookeeperTransporter)实例化一个注册中心

publicZookeeperRegistry(URLurl,ZookeeperTransporterzookeeperTransporter){\nsuper(url);\nif(url.isAnyHost()){\nthrownewIllegalStateException(&34;);\n}\n\n//获取组名,默认为dubbo\nStringgroup=url.getParameter(Constants.GROUP_KEY,DEFAULT_ROOT);\nif(!group.startsWith(Constants.PATH_SEPARATOR)){\n//group=&34;+group\ngroup=Constants.PATH_SEPARATOR+group;\n}\nthis.root=group;\n//创建Zookeeper客户端,默认为CuratorZookeeperTransporter\nzkClient=zookeeperTransporter.connect(url);\n//添加状态监听器\nzkClient.addStateListener(newStateListener(){\n@Override\npublicvoidstateChanged(intstate){\nif(state==RECONNECTED){\ntry{\nrecover();\n}catch(Exceptione){\nlogger.error(e.getMessage(),e);\n}\n}\n}\n});\n}

在上面的代码代码中,我们重点关注ZookeeperTransporter的connect方法调用,这个方法用于创建Zookeeper客户端。创建好Zookeeper客户端,意味着注册中心的创建过程就结束了。接下来,再来分析一下Zookeeper客户端的创建过程。

publicZookeeperClientconnect(URLurl){\n//创建CuratorZookeeperClient\nreturnnewCuratorZookeeperClient(url);\n}

继续向下看。

publicclassCuratorZookeeperClientextendsAbstractZookeeperClient<CuratorWatcher>{\n\nprivatefinalCuratorFrameworkclient;\n\npublicCuratorZookeeperClient(URLurl){\nsuper(url);\ntry{\n//创建CuratorFramework构造器\nCuratorFrameworkFactory.Builderbuilder=CuratorFrameworkFactory.builder()\n.connectString(url.getBackupAddress())\n.retryPolicy(newRetryNTimes(1,1000))\n.connectionTimeoutMs(5000);\nStringauthority=url.getAuthority();\nif(authority!=null&&authority.length()>0){\nbuilder=builder.authorization(&34;,authority.getBytes());\n}\n//构建CuratorFramework实例\nclient=builder.build();\n//省略无关代码\n\n//启动客户端\nclient.start();\n}catch(Exceptione){\nthrownewIllegalStateException(e.getMessage(),e);\n}\n}\n}

CuratorZookeeperClient构造方法主要用于创建和启动CuratorFramework实例。至此Zookeeper客户端就已经启动了

下面我们将Dubbo的demo跑起来,然后通过Zookeeper可视化客户端ZooInspector查看节点数据。如下:

从上图中可以看到DemoService这个服务对应的配置信息最终被注册到了zookeeper节点下。搞懂了服务注册的本质,那么接下来我们就可以去阅读服务注册的代码了。

protectedvoiddoRegister(URLurl){\ntry{\n//通过Zookeeper客户端创建节点,节点路径由toUrlPath方法生成,路径格式如下:\n///${group}/${serviceInterface}/providers/${url}\n//比如\n///dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1……\nzkClient.create(toUrlPath(url),url.getParameter(Constants.DYNAMIC_KEY,true));\n}catch(Throwablee){\nthrownewRpcException(&34;);\n}\n}

如上,ZookeeperRegistry在doRegister中调用了Zookeeper客户端创建服务节点。节点路径由toUrlPath方法生成,该方法逻辑不难理解,就不分析了。接下来分析create方法,如下:

publicvoidcreate(Stringpath,booleanephemeral){\nif(!ephemeral){\n//如果要创建的节点类型非临时节点,那么这里要检测节点是否存在\nif(checkExists(path)){\nreturn;\n}\n}\ninti=path.lastIndexOf(&39;);\nif(i>0){\n//递归创建上一级路径\ncreate(path.substring(0,i),false);\n}\n\n//根据ephemeral的值创建临时或持久节点\nif(ephemeral){\ncreateEphemeral(path);\n}else{\ncreatePersistent(path);\n}\n}

好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。

总结

在有注册中心,需要注册提供者地址的情况下,ServiceConfig解析出的URL格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode(&34;)基于DubboSPI的自适应机制,通过URLregistry://协议头识别,就调用RegistryProtocol34;dubbo://service-host/{服务名}/{版本号}&export()方法,开发服务端口RegistryProtocol34;temp&34;localhost&34;Failtocreateremotingclientforservice…&34;url==null&34;*&34;,&34;,&34;interfaces&1827\nif(!invoker.getInterface().equals(GenericService.class)&&generic){\nintlen=interfaces.length;\nClass<?>[]temp=interfaces;\n//创建新的interfaces数组\ninterfaces=newClass<?>[len+1];\nSystem.arraycopy(temp,0,interfaces,0,len);\n//设置GenericService.class到数组中\ninterfaces[len]=GenericService.class;\n}\n\n//调用重载方法\nreturngetProxy(invoker,interfaces);\n}\n\npublicabstract<T>TgetProxy(Invoker<T>invoker,Class<?>[]types);

如上,上面大段代码都是用来获取interfaces数组的,我们继续往下看。getProxy(Invoker,Class<?>[])这个方法是一个抽象方法,下面我们到JavassistProxyFactory类中看一下该方法的实现代码。

public<T>TgetProxy(Invoker<T>invoker,Class<?>[]interfaces){\n//生成Proxy子类(Proxy是抽象类)。并调用Proxy子类的newInstance方法创建Proxy实例\nreturn(T)Proxy.getProxy(interfaces).newInstance(newInvokerInvocationHandler(invoker));\n}

上面代码并不多,首先是通过Proxy的getProxy方法获取Proxy子类,然后创建InvokerInvocationHandler对象,并将该对象传给newInstance生成Proxy实例。InvokerInvocationHandler实现JDK的InvocationHandler接口,具体的用途是拦截接口类调用。下面以org.apache.dubbo.demo.DemoService这个接口为例,来看一下该接口代理类代码大致是怎样的(忽略EchoService接口)。

packageorg.apache.dubbo.common.bytecode;\n\npublicclassproxy0implementsorg.apache.dubbo.demo.DemoService{\n\npublicstaticjava.lang.reflect.Method[]methods;\n\nprivatejava.lang.reflect.InvocationHandlerhandler;\n\npublicproxy0(){\n}\n\npublicproxy0(java.lang.reflect.InvocationHandlerarg0){\nhandler=$1;\n}\n\npublicjava.lang.StringsayHello(java.lang.Stringarg0){\nObject[]args=newObject[1];\nargs[0]=($w)$1;\nObjectret=handler.invoke(this,methods[0],args);\nreturn(java.lang.String)ret;\n}\n}

好了,到这里代理类生成逻辑就分析完了。整个过程比较复杂,大家需要耐心看一下。

总结

从注册中心发现引用服务:在有注册中心,通过注册中心发现提供者地址的情况下,ReferenceConfig解析出的URL格式为:registry://registry-host:/org.apache.registry.RegistryService?refer=URL.encode(&34;)。通过URL的registry://协议头识别,就会调用RegistryProtocolrefer()方法,创建一个ExchangeClient客户端并返回DubboInvoker实例由于一个服务可能会部署在多台服务器上,这样就会在providers产生多个节点,这样也就会得到多个DubboInvoker实例,就需要RegistryProtocol调用Cluster将多个服务提供者节点伪装成一个节点,并返回一个InvokerInvoker创建完毕后,调用ProxyFactory为服务接口生成代理对象,返回提供者引用

关于视频网站解析源码分享,视频网址解析原理的介绍到此结束,希望对大家有所帮助。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平