dubbo通过xml进行服务暴露源码解析
在服务提供者进行服务暴露有两种方式:配置xml和引用注解这两种方式,这次讲解的是通过xml方式
该过程主要有两步:解析xml创建bean和怎么根据bean的信息进行对应的暴露操作
1、解析xml创建bean
当我们配置了xml之后就了解怎么去解析配置的xml,在启动项目时,通过springboot框架监听相对应的事件就会去执行解析xml的操作。
但DUBBO首先在DubboNamespaceHandler中注册自定义的xml解析器,在解析到xml中dubbo标签时就会去调用该解析器将每一项配置组装成ServiceBean对象
这是<service:>标签的解析的注册语句
据beanClass(ServiceBean)获取set方法,把传进来的标签element里的属性(如id、interface、class等)赋值给beanDefinition对象
这里beanDefinition的定义是记录着需要实例化bean的各种信息,相当于模子,有了模子就可以实例化相应的bean出来,返回的beanDefinition最终会放到spring一个beanDefinitionMap<String, BeanDefinition>中,其中key为xml定义的id
在org.springframework.beans.factory.support.DefaultListableBeanFactory中,当初次调用容器的getBean(beanName)时就会通过beanDefinitionMap获取BeanDefinition去实例化bean,在这里将会去实例化ServiceBean实例,实例化的seriveBean归由spring容器管理,第一步就到此为至了。
2、如何去暴露服务
SeriveBean实现ApplicationListener<ContextRefreshedEvent>接口,在监听到容器触发相对应的事件调用onApplicationEvent()方法,然后调用父类的export()方法
//判断是否是服务端服务,判断这个服务是否暴露以及通过ScheduledThreadPoolExecutor
这个线程池类支持延迟暴露
public synchronized void export() { if (provider != null) { if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } if (export != null && !export) { return; } if (delay != null && delay > 0) { delayExportExecutor.schedule(new Runnable() { @Override public void run() { doExport(); } }, delay, TimeUnit.MILLISECONDS); } else { doExport(); } }
接下来就是doExport()方法,在这个方法的前面就是做一些 check 操作,不是重点就不一一分析了。我们主要看一下它的appendProperties方法
以及doExportUrls
这两个方法。appendProperties()
方法主要是为当前对象通过setter 方法来添加属性,它主要是通过以下方式来添加属性:
- 从 System 中获取属性key值的优先通讯是:
dubbo.provider.
+ 当前类 Id + 当前属性名称 >dubbo.provider.
+ 当前属性名称 为 key 获取值. - 首先从特定properties文件加载属性:首先
System.getProperty("dubbo.properties.file")
获取到文件路径,如果获取不到就会试图加载 dubbo 的默认的路径dubbo.properties
加载。获取属性的 key 和上面从 System 里面获取的规则一样。
然后我们就来分析一下doExportUrl
这个方法,因为 Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。所以这里需要循环各个协议进行多协议暴露服务。
private void doExportUrls() { List<URL> registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
接下来解析doExportUrlsFor1Protocol方法,该方法中appendParameters(Map, Object)的作用通过调用当前对象的getter方法获取到传入对象的值然后塞到 map 当中去。用于后面的构造URL
这个对象。
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
接下来的执行就是服务的暴露,分为本地暴露和远程暴露
本地暴露:本地暴露是暴露在JVM中,一个服务可能既是provider,又是consumer,自己调用自己的服务时不需要网络通信,每个服务默认都会在本地暴露,url是以injvm开头
执行本地暴露的方法是exportLocal(url),进入方法中
private void exportLocal(URL url) {
//先判断,如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出 if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(LOCALHOST) .setPort(0);
//创建一个新的 URL 并将协议头、主机名以及端口设置成新的值 ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref)); Exporter<?> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
//proxyFactory.getInvoker()方法是生成一个Invoker对象,并调用 InjvmProtocol 的 export 方法导出服务。进入里面可以看到,在导出过程中利用exporterMap缓存了exporter exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); } }
远程暴露:将IP,端口等信息暴露给远程客户端,调用时需要网络通信
与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程
//生成invoker对象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker);
debug的时候可以看到protocol的实现类RegistryProtocol,进入export方法
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//先进行服务导出,再进行服务暴露 //export invoker final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);if (register) {
//服务注册,它传递了两个参数,registryUrl(zookeeper注册中心地址)和registedProviderUrl(服务暴露地址) register(registryUrl, registedProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); }
......
}
再debug进入doLocalExport()方法
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) { String key = getCacheKey(originInvoker); ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);//获取缓存的exporter对象 if (exporter == null) {//判断,单例模式执行 synchronized (bounds) { exporter = (ExporterChangeableWrapper<T>) bounds.get(key); if (exporter == null) { final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker)); exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);//这里的实现类是dubboProtocol,通过该类的export获取exporter bounds.put(key, exporter); } } } return exporter; }
我们debug到dubboProtocol的export()方法中 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl(); // export service. String key = serviceKey(url); DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);//创建导出对象 exporterMap.put(key, exporter); //export an stub service for dispatching event ........
//dubbo底层是用netty的,创建server服务器,绑定本机网卡地址:通信端口,用于被调用服务的通信,具体的创建方法NettyServer#doOpen openServer(url); optimizeSerialization(url); return exporter; }
我们可以返回服务注册了,创建zk客户端,创建对应节点,将服务元数据保存在zk上面。
参考网址:
https://blog.csdn.net/peace_hehe/article/details/79288053
https://blog.csdn.net/aoomiao/article/details/83503223