Dubbo2.7源碼分析-如何發布服務

Dubbo的服務發布邏輯是比較復雜的,我還是以Dubbo自帶的示例講解,這樣更方便和容易理解。

Provider配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
 
    <!-- 提供方應用信息,用于計算依賴關系 -->
    <dubbo:application name="hello-world-app"  />
 
    <!-- 使用multicast廣播注冊中心暴露服務地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />
 
    <!-- 用dubbo協議在20880端口暴露服務 -->
    <dubbo:protocol name="dubbo" port="20880" />
 
    <!-- 聲明需要暴露的服務接口 -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
 
    <!-- 和本地bean一樣實現服務 -->
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>

ApplicationContext

ClassPathXmlApplicationContext父類AbstractApplicationContext的方法refresh()在實例化bean之后的最后一步finishRefresh()中,此方法作用是發布相應的事件。

    protected void finishRefresh() {
        //省略LifeCycleProcessor刷新代碼

        // Publish the final event.
        publishEvent(new ContextRefreshedEvent(this));

        // 省略注冊到 LiveBeansView MBean代碼
    }

可以看到發布了一個ContextRefreshedEvent事件。

    protected void publishEvent(Object event, ResolvableType eventType) {
        //省略部分代碼
      getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
            //省略部分代碼

首先獲取ApplicationEvent事件廣播對象,然后廣播事件。

ApplicationEvent事件廣播對象默認是SimpleApplicationEventMulticaster,這個對象是在AbstractApplicationContext的方法initApplicationEventMulticaster()初始化的,如果需要自定義,可以實現接口ApplicationEventMulticaster,并將bean的名字命名為applicationEventMulticaster

接下來看看SimpleApplicationEventMulticaster類的multicastEvent方法。

    @Override
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
                //事件類型
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
               
               //applicationListener
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
                       //異常執行
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        invokeListener(listener, event);
                    }
                });
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

可以看到此方法會調用applicationListener的方法,對于Dubbo而言,就是ServiceBean.

怎么樣獲取到ServiceBean的呢?

ServiceBean實現了好幾個接口,其中有兩個接口ApplicationContextAwareApplicationListener<ContextRefreshedEvent>,其中ApplicationContextAware使ServiceBean具有獲取ApplicationContext的能力(了解bean的生命周期),而ApplicationListener使ServiceBean具有響應事件響應的能力。dubbo實現ApplicationContextAware的目的是通過反射把自己添加到ApplicationContext的ApplicationListener列表中,即使不實現ApplicationContextAware接口,spring也會將實現了ApplicationListener接口的bean添加到其listener列表中的,dubbo這樣做估計是向后兼容。

接著看invokeListener(listener, event);方法

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            doInvokeListener(listener, event);
        }
    }

    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            //省略異常處理
        }
    }

invokeListener方法內部調用了doInvokeListener方法,而doInvokeListener方法調用了listener(ServiceBean)的onApplicationEvent方法.

ServiceBean

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

onApplicationEvent方法調用了export方法,export方法首先判斷是否已經發布了服務,發布了則直接返回,沒有發布則會判斷是否需要延遲發布,如果需要延遲,則將發布服務做為一個任務添加到ScheduledThreadPoolExecutor線程池中,如果不延遲,則調用doExport方法立即發布服務。
doExport方法中會獲取application/registries/monitor/module/protocols,并做一些檢查和屬性填充,然后調用doExportUrls();發布服務。doExportUrls()首先調用loadRegistries方法得到要注冊的url,然后發布相關Protocol的服務。

簡單敘述一下獲取url的過程,url通過map組裝參數和對應的值,參數有ApplicationConfigRegistryConfig對象的屬性以及pathdubbotimestamppidprotocolregistry

本示例applicationConfig是:

<dubbo:application name="demo-provider" qosPort="22222" id="demo-provider" />

registryURL
registryConfig是:

<dubbo:registry address="multicast://224.5.6.7:1234" id="org.apache.dubbo.config.RegistryConfig" />

最終map組裝結果是:
url parameters

最后得到registryURL是:

registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=4892&qos.port=22222&registry=multicast&timestamp=1536112339884

然后調用doExportUrlsFor1Protocol方法發布服務,此方法開始部分是構造發布的服務URL,然后再發布url。

服務URL
URL包括以下幾部分:服務端還是客戶端標識Dubbo版本,時間戳,Pid,服務的方法名tokenApplicationConfig,MoudleConfig,ProviderConfig,ProtocolConfig,*MethodConfig對象的相關屬性等。
例如本示例的url:

dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8004&qos.port=22222&side=provider&timestamp=1536114090787

我們來著重看一下在構造URL過程中port的獲取過程。

//protocolConfig是配置的<dubbo:protocol />生成的對象
//name是protocol的name,本示例為"dubbo"
//map保存了url的鍵值對
Integer port = this.findConfigedPorts(protocolConfig, name, map);

findConfigedPorts顧名思義是查找配置的port,從哪查呢,先從系統環境變量查,如果沒找到,再查找名字為name的protocol協義。

    private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map<String, String> map) {
        Integer portToBind = null;

        // 從環境變量從查找綁定的port
        String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);
        portToBind = parsePort(port);

        // 如果沒有從環境變量從查到,則從名稱為name的protocol查找
        if (portToBind == null) {
            portToBind = protocolConfig.getPort();
            if (provider != null && (portToBind == null || portToBind == 0)) {
                portToBind = provider.getPort();
            }
           //這一句是關鍵,示例中name值是"dubbo",所以會實例化DubboProtocol,得到默認的port:20880
            final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
            if (portToBind == null || portToBind == 0) {
                portToBind = defaultPort;
            }
            if (portToBind == null || portToBind <= 0) {
                portToBind = getRandomPort(name);
                if (portToBind == null || portToBind < 0) {
                    portToBind = getAvailablePort(defaultPort);
                    putRandomPort(name, portToBind);
                }
                logger.warn("Use random available port(" + portToBind + ") for protocol " + name);
            }
        }

        //保存port到map中,以便后面url使用
        map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));

        // 從環境變量中查找注冊的port,如果沒有找到,則等于綁定的Port.
        String portToRegistryStr = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_REGISTRY);
        Integer portToRegistry = parsePort(portToRegistryStr);
        if (portToRegistry == null) {
            portToRegistry = portToBind;
        }
        return portToRegistry;
    }

有人或許有疑問,ServiceConfig在實例化時,不是已經加載過Protocol了嗎?為什么還要使用ExtensionLoader加載一次呢?

final int defaultPort =ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();

答: ServiceConfig實例化時,加載的Protocol是自適應的Protocol,是動態生成的,類名是Protocol$Adaptive(見Dubbo源碼分析-SPI的應用中有分析)。而這里獲取Port時加載的也是Protocol類,但指名了具體加載的是哪個Protocol(本示例是名稱為dubbo的Protocol,即DubboProtocol,此類默認的端口是20880)。

發布URL

發布本地服務

調用ServiceConfig類的exportLocal(URL url)發布本地服務。

    private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
              //本地服務url
               URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            
            
           ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }

本示例的本地服務 url是:

injvm://127.0.0.1/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=3008&qos.port=22222&side=provider&timestamp=1536125473655

重點看這一句:

Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));

其中涉及到ProxyFactory和Protocol,下面分別來看一看。

ProxyFactory

proxyFactory也是通過SPI加載的自適應類對象,類名為ProxyFactory$Adaptive,我們來看一下其class文件反編譯后的源碼。

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class ProxyFactory$Adaptive implements ProxyFactory {
    public ProxyFactory$Adaptive() {
    }

    public Invoker getInvoker(Object var1, Class var2, URL var3) throws RpcException {
        if (var3 == null) {
            throw new IllegalArgumentException("url == null");
        } else {
            String var5 = var3.getParameter("proxy", "javassist");
            if (var5 == null) {
                throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var3.toString() + ") use keys([proxy])");
            } else {
                ProxyFactory var6 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var5);
                return var6.getInvoker(var1, var2, var3);
            }
        }
    }

    public Object getProxy(Invoker var1, boolean var2) throws RpcException {
        if (var1 == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        } else if (var1.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        } else {
            URL var3 = var1.getUrl();
            String var4 = var3.getParameter("proxy", "javassist");
            if (var4 == null) {
                throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var3.toString() + ") use keys([proxy])");
            } else {
                ProxyFactory var5 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var4);
                return var5.getProxy(var1, var2);
            }
        }
    }

    public Object getProxy(Invoker var1) throws RpcException {
        if (var1 == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        } else if (var1.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        } else {
            URL var2 = var1.getUrl();
            String var3 = var2.getParameter("proxy", "javassist");
            if (var3 == null) {
                throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + var2.toString() + ") use keys([proxy])");
            } else {
                ProxyFactory var4 = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(var3);
                return var4.getProxy(var1);
            }
        }
    }
}

其中有三個方法,兩個獲取代理,一個獲取Invoker。我們來看其中的getInvoker方法,默認獲取名稱為javassist的ProxyFactory。
由于本地服務URL中沒有proxy參數,所以會調用JavassistProxyFactory的getInvoker(T proxy, Class<T> type, URL url)方法,返回AbstractProxyInvoker的匿名類對象,此對象代理了服務對象(本示例中為DemoServiceImpl對象)。

其實(ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension("javassist");獲取到的并不是JavassistProxyFactory對象,而是StubProxyFactoryWrapper對象,為什么呢?我們可以看下ExtensionLoader的getExtension(String name)方法

   public T getExtension(String name) {
       //檢查name是否合法
       if (name == null || name.length() == 0)
           throw new IllegalArgumentException("Extension name == null");
      //如果name等于true,則加載SPI的默認插件
     if ("true".equals(name)) {
         return getDefaultExtension();
      }
      //從當前插件類的緩存實例對象中獲取
      Holder<Object> holder = cachedInstances.get(name);
      if (holder == null) {
         cachedInstances.putIfAbsent(name, new Holder<Object>());
         holder = cachedInstances.get(name);
     }
     Object instance = holder.get();
     if (instance == null) {
       synchronized (holder) {
             instance = holder.get();
             if (instance == null) {
                 //創建插件實例
                 instance = createExtension(name);
                 holder.set(instance);
             }
         }
     }
     return (T) instance;
 }

   private T createExtension(String name) {
      //從文件目錄中加載插件類
      Class<?> clazz = getExtensionClasses().get(name);
      if (clazz == null) {
         throw findException(name);
    }
   
    //從已加載的所有插件實例集合中獲取
     try {
         T instance = (T) EXTENSION_INSTANCES.get(clazz);
         if (instance == null) {
             //實例化插件實例,并放入集合
             EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
         
        //注入屬性
        injectExtension(instance);

        //插件的包裹類
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
           for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
 }

重點的地方就在于插件的包裹類,StubProxyFactoryWrapper就是JavassistProxyFactory的包裹類,為什么這么說呢,因為StubProxyFactoryWrapper有一個帶ProxyFactory參數的構造函數而且實現了ProxyFactory接口,具體可以看Extension的loadExtensionClasses方法源碼(裝飾者模式)。

Protocol

protocol對象也是一個自適應插件類,類名為Protocol$Adaptive,在上一篇文章中已有講解。這個類會根據url的協義取得對應轉義的插件類,沒有的話,默認為dubbo協義,本地服務url協義為injvm,所以會加載InjvmProtocol,但是在加載InjvmProtocol并實例化后,發現InjvmProtocol還有對應的包裹類即(其實是所有Protocol的包裹類):ProtocolFilterWrapper和ProtocolListenerWrapper。ProtocolFilterWrapper類的作用是添加一些過濾器,ProtocolListenerWrapper的作用是添加ExporterListener。InjvmProtocol的export方法僅僅創建一個InjvmExporter實例,沒有開啟服務。

發布遠程服務

如果注冊url不為空,調用proxyFactory得到服務對象的代理類,然后使用protocol發布服務。由于注冊url的協義是registry,在使用ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension("registry");會加載RegistryProtocol類并實例化,而且會添加其包裹類:ProtocolFilterWrapper和ProtocolListenerWrapper。而在這兩個包裹類的export方法的首行,都會對registry協義進行單獨處理。

RegistryProtocol
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }

經過這兩個包裹類后,最終會調用RegistryProtocol的export方法。

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //發布服務
       //originInvoker中包含了代理服務對象的代理類
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

        //注冊相關代碼省略

        //訂閱相關代碼省略
    }

       private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
       
         //key為發布的服務url
        String key = getCacheKey(originInvoker);
        //從map緩存中獲取
        ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
      //double check
        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);
                    bounds.put(key, exporter);
                }
            }
        }
        return exporter;
    }

最重要的是這一句:

exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);

其中protocol也是Protocol$Adaptive對象,而invokerDelegete的URL是服務的url.

本示例中為:

dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8468&qos.port=22222&side=provider&timestamp=1536138127517

DubboProtocol

Protocol$Adaptive在解析URL的時得到dubbo,所以會加載DubboProtocol并實例化(DubboProtocol實際在前面獲取默認接口時已經實例化并緩存起來了,此處取的是緩存的實例),并調用了DubboProtocol的export方法(與上面一樣,在得到DubboProtocol實例后,仍然會在外面包裹一下)。

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // 服務名:本例中為org.apache.dubbo.demo.DemoService:20880
        String key = serviceKey(url);
       //exporter 控制服務打開與關閉
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //省略發布子服務的相關代碼
       
       //打開服務
        openServer(url);
       //優化序列化處理
        optimizeSerialization(url);
        return exporter;
    }

經過層層探索,曲折迂回,終于到openServer了,進去看看。

    private void openServer(URL url) {
        // 服務ip:端口號
        String key = url.getAddress();
        
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // 服務支持重置
                server.reset(url);
            }
        }
    }

可以看到其中有一個重要方法createServer(url)。

    private ExchangeServer createServer(URL url) {
        // 當服務關閉時,默認啟動發送只讀事件
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        // 默認啟動心跳
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

        //str默認為netty
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        
        //添加編解碼器
        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        ExchangeServer server;
        try {
            //啟動服務,并傳入請求處理器
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
       
         //判斷客戶端使用的是網絡傳輸層框架是否支持服務端的網絡傳輸層。
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }
Exchangers

進入Exchangers.bind方法一探究竟。

    public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        //如果編碼碼器沒有,則添加參數exchange
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        return getExchanger(url).bind(url, handler);
    }

getExchanger(url)默認得到的是HeaderExchanger,可通過exchanger參數配置。
到HeaderExchanger中看看bind方法

    public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
Transporter

看下Transporters的bind方法。

    public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().bind(url, handler);
    }

通過getTransporter方法獲取一個自適應的Transporter,類名為Transporter$Adaptive,我們來看一下其源碼:

package org.apache.dubbo.remoting;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Transporter$Adaptive implements Transporter {
    public Transporter$Adaptive() {
    }

    public Client connect(URL var1, ChannelHandler var2) throws RemotingException {
        if (var1 == null) {
            throw new IllegalArgumentException("url == null");
        } else {
            String var4 = var1.getParameter("client", var1.getParameter("transporter", "netty"));
            if (var4 == null) {
                throw new IllegalStateException("Fail to get extension(org.apache.dubbo.remoting.Transporter) name from url(" + var1.toString() + ") use keys([client, transporter])");
            } else {
                Transporter var5 = (Transporter)ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(var4);
                return var5.connect(var1, var2);
            }
        }
    }

    public Server bind(URL var1, ChannelHandler var2) throws RemotingException {
        if (var1 == null) {
            throw new IllegalArgumentException("url == null");
        } else {
            String var4 = var1.getParameter("server", var1.getParameter("transporter", "netty"));
            if (var4 == null) {
                throw new IllegalStateException("Fail to get extension(org.apache.dubbo.remoting.Transporter) name from url(" + var1.toString() + ") use keys([server, transporter])");
            } else {
                Transporter var5 = (Transporter)ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(var4);
                return var5.bind(var1, var2);
            }
        }
    }
}

可以看到Transporter$Adaptive通過判斷URL中是否有transporter參數,如果沒有,就默認為netty。

示例中服務的URL為

dubbo://192.168.124.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.124.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=8004&qos.port=22222&side=provider&timestamp=1536114090787

其中沒有transporter參數,所以就使用netty。然后dubbo就去查找netty對應的是哪個Transporter,結果找到是NettyTransporter。

package org.apache.dubbo.remoting.transport.netty4;

//省略導入部分

public class NettyTransporter implements Transporter {

    public static final String NAME = "netty";

    @Override
    public Server bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

    @Override
    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }

}

NettyTransporter很簡單,只有兩個方法,一個用于開啟服務,一個用于連接服務。到這里已經明白了Dubbo是如何發布一個服務的。

我們再進一步看下NettyServer的構造函數

    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }

可以看出其調用父類的構造函數,并傳入url和handler的包裹類。handler的包裹類有哪些呢,進去看一看。

    public static ChannelHandler wrap(ChannelHandler handler, URL url) {
        return ChannelHandlers.getInstance().wrapInternal(handler, url);
    }
    
     protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
                .getAdaptiveExtension().dispatch(handler, url)));
    }

注意到有一個接口Dispatcher,其自適應插件類是AllDispatcher,AllDispatcher的dispatch方法返回AllChannelHandler實例(此實例會將所有請求做為任務放入線程池中處理),在此實例基礎上又包裹了HeartbeatHandlerMultiMessageHandler
NettyServer會將MultiMessageHandler層層往上傳到其父類AbstractPeer

我們來回憶一下正向流程

從ServiceConfig發布registryURL開始(見doExportUrlsFor1Protocol方法)
1.ServiceConfig生成服務實例的代理工廠類JavassistProxyFactory(ProxyFactory SPI默認代理工廠類)并包裹到DelegateProviderMetaDataInvoker(此類記錄代理工廠類和服務信息ServiceBean(<dubbo:service />標簽對應的類))
2.由于registryURL的protocol協義是registry,所以會加載RegistryProtocol(Protocol類的外面都包裹了ProtocolFilterWrapper和ProtocolListenerWrapper,下面不再特殊說明),并傳入上一步的invoker。
3.RegistryProtocol又找到DubboProtocol,也會帶上Invoker(此時的Invoker包含上一次的Invoker并帶有服務地址(dubbo://IP:端口/服務接口全稱?參數=xxx))。

所以requestHandler又會調用正向傳過來的Invoker,經過ProtocolFilterWrapper和ProtocolListenerWrapper,最終調用到服務實現類相應的方法。

最后以一張圖總結:

標識為SPI的類,是可以動態加載的。圖片看不清楚的話,請查看原圖

再簡單說下接收到請求后的處理流程:NettyServer接收到請求后,交給NettyServerHandler處理,NettyServerHandler轉交給NettyServer的父類AbstractPeer處理,AbstractPeer又交給MultiMessageHandler處理,這樣就開始了handler鏈的處理,handler的終點是HeaderExchangerHandler,HeaderExchangerHandler調用DubboProtocol傳過來的成員變量requestHandler調用相應的服務類方法,然后得到結果,調用NettyServerHandler傳過來的NettyChannel發送結果到Client。

用力不如用心!用心寫好每一篇文章!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375