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

原文地址:https://www.cnblogs.com/qizhufeitian/p/13943951.html