天天看點

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

前言

源碼位址:dubbo

版本: dubbo-parent pom中的version為

2.7.12-SNAPSHOT

測試代碼:dubbo-demo module。在application.properties中,加入了registry url如下

dubbo.registry.address=zookeeper://127.0.0.1:2181

在dubbo中一切都是基于interface而來,對provider,consumer都是。在代碼層面上對應AbstractInterfaceConfig,而接口又是以方法粒度擴充對應AbstractMethodConfig。

從代碼層次上來看

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

AbstractServiceConfig:與服務提供方有關

  • ServiceConfig -> 對應的spring bean為子類ServiceBean
  • ProviderConfig

AbstractReferenceConfig:與服務消費方有關

  • ReferenceConfig->對應的spring bean為子類ReferenceBean
  • ConsumerConfig

啟動入口

DubboBootstrapApplicationListener :繼承自 OnceApplicationContextEventListener,該監聽器隻觸發一次容器事件監聽的執行避免事件傳播,适合用于容器初始化完成和關閉等,定義的order為最低優先級保證當容器完全初始完成後最後執行。

onContextRefreshedEvent((ContextRefreshedEvent) event);

onContextClosedEvent((ContextClosedEvent) event);

當spring容器初始化完成後,該監聽器會監聽到ContextRefreshedEvent事件并執行DubboBootstrap的start,其中包括初始化、暴露服務、引用服務等所有流程,本次主要分析服務暴露的部分。

initialize:負責與dubbo相關的服務初始化,例如startConfigCenter會使用之前存放了相關配置資訊(ApplicationConfig,ConfigCenterConfig等)的ConfigManager,将合适configCenter作為DynamicConfiguration存到environment

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

服務釋出

DubboBootstrapApplicationListener監聽到容器啟動事件ContextRefreshedEvent

調用DubboBootstrap的start,其中與服務釋出有關的為以下幾個流程

exportServices

負責釋出掃描到的dubbo service提供者。

DubboBootstrap中由configManager負責收集跟dubbo相關的服務,注冊,協定等資訊,并在此時直接定位要釋出服務ServiceConfig;

根據需要異步或同步釋出服務(ExecutorRepository也是dubbo擴充加載類的一種,利用executor異步處理釋出每個service的任務);

exportService(ServiceConfig sc):執行ServiceConfig的export,并緩存到Map<String, ServiceConfigBase<?>> exportedServices中

ServiceConfig export

釋出檢驗

比如是否設定了export為false,需要delay等

doExport

主要功能為doExportUrls。這裡采用zookeeper注冊中心為例

// 構造registry相關資訊
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
           

拿到的registry url例如:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&id=org.apache.dubbo.config.RegistryConfig#0&pid=13300&registry=zookeeper&timestamp=1627216227129;

依次對配置的protocols解析得到的ProtocolConfig,得到該服務對應在注冊中心上(這裡為zk)的path,一般為接口全路徑名例如org.apache.dubbo.demo.DemoService, 并添加到ServiceRepository緩存中;調用doExportUrlsFor1Protocol處理對應協定的釋出。

doExportUrlsFor1Protocol

  1. 構造url所需要的參數資訊:比如side(provider),application,interface,methods及運作時資訊等
  2. 對GenericService的情況進行處理:非generic時,會通過Wrapper找到該接口下對應要暴露的方法等,生成第一個interface->Wrapper0類存在于Wrapper的緩存中
if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
          ....
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
           
  1. 解析host和port:用于定位ip和端口被消費方調用
  2. 構造服務方URL:用于服務方在注冊中心的位址,例如,dubbo://172.xx.xx.x:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.xx.xx.x&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=13300&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider&timestamp=1627219556526
  3. ConfiguratorFactory:提供SPI接口擴充處理url參數資訊的api,Configurator getConfigurator(URL url)
  4. scope決定釋出類型:由目前url中的scope參數判斷如何釋出,none則不釋出,不是remote(null的情況也比對)則exportLocal(這裡将執行),再接着不是local(null的情況也比對)則進行遠端釋出(這裡也會執行);

對第六步中不同方式釋出進行下一步分析

exportLocal

用local URL的方式通過Protocol$Adaptive釋出

//ServiceConfig中預設初始化了protocol和proxyFactory自适應代理類
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

//exportLocal中,這裡實際執行StubProxyFactoryWrapper和InjvmProtocol的接口方法
Exporter<?> exporter = PROTOCOL.export(PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
           

其中StubProxyFactoryWrapper是個包裝類,裡面包裹的真正實作是JavassistProxyFactory(因為ProxyFactory的擴充目錄中有符合條件的包裝類StubProxyFactoryWrapper對其進行自動封裝),是以會真正執行Javasist的export,構造了一個Wrapper類作為實際執行invoke的AbstractProxyInvoker中的delegate。

final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
           

Wrapper中以類加載器AppClassLoader加載并初始化類ClassGenerator,通過動态java代碼拼接的方式,為接口暴露的方法進行了處理。

$1,$2等為占位符,表示目前方法中的第幾個參數($1:Object o, $2:String n,$3:Class[] p)

public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
            org.apache.dubbo.demo.provider.DemoServiceImpl w;
            try {
                w = ((org.apache.dubbo.demo.provider.DemoServiceImpl) $1);
            } catch (Throwable e) {
                throw new IllegalArgumentException(e);
            }
            try {
                if ("sayHello".equals($2) && $3.length == 1) {
                    return ($w) w.sayHello((java.lang.String) $4[0]);
                }
                if ("sayHelloAsync".equals($2) && $3.length == 1) {
                    return ($w) w.sayHelloAsync((java.lang.String) $4[0]);
                }
            } catch (Throwable e) {
                throw new java.lang.reflect.InvocationTargetException(e);
            }
            throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
        }
           

通過預設構造函數執行個體化對象(Wrapper) wc.getDeclaredConstructor().newInstance(),得到執行個體化後的wrapper 為Wrapper子類,并緩存到Wrapper的map即WRAPPER_MAP,此時interface及對應的實作類都已經在該map裡存在

interface org.apache.dubbo.demo.DemoService->Wrapper0

class org.apache.dubbo.demo.provider.DemoServiceImpl -> Wrapper1

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

得到由proxyFactory包裝好的Invoker後,同樣在調用protocol的export也經過了多個包裝類的層層調用(ProtocolFilterWrapper->ProtocolListenerWrapper->InjvmProtocol)。

ProtocolFilterWrapper

:實作了項目中配置的Filter擴充接口內建,對非注冊url的服務釋出進行Invoker轉換,擴充為繼承Invoker的FilterNode(是一個連接配接父子FilterNode的過濾鍊類,可以依次對filter進行執行),再交由ProtocolListenerWrapper繼續執行;

ProtocolListenerWrapper

:隻對非注冊url進行處理,将具體Protocol實作(這裡為InjvmProtocol)傳回的invoker包裝為ListenerExporterWrapper(裡面增加了ExporterListener接口擴充,可自定義做監聽釋出事件的任務)

InjvmProtocol

:是最終負責實作export的邏輯,将invoker封裝為InjvmExporter,并加入到AbstractProtocol的exporterMap緩存。 key:org.apache.dubbo.demo.DemoService,value:InjvmExporter

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

将得到的exporter加入到目前ServiceConfig中的exporters中(ArrayList),表明目前服務已經釋出,至此exportLocal已經結束。

remote export

與exportLocal相似,通過合适的ProxyFactory動态選擇了某一實作的getInvoker獲得invoker并經過包裝,由Protocol$Adaptive類選擇目前url中protocol對應的協定即RegistryProtocol實作進行釋出,将釋出成功後的exporter進行緩存到目前集合exporters中

for (URL registryURL : registryURLs) {
	//這裡實際上将原provider url資訊作為新registryURL的一個參數export附加上去,在registryProtocol的export中會取出來再次利用
	Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
	DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
	
    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
    exporters.add(exporter);
}
           
ProxyFactory Invoker

根據registryURLs,對每個非injvm的registryURL進行注冊協定資訊的構造,根據url中proxy參數資訊會由JavasistProxyFactory實作生成invoker。(如果url中的protocol為injvm,不需要注冊到遠端服務中心上)

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

可以看到invoker對象的wrapper,複用了之前exportLocal時已産生并存在緩存中的同個接口實作類的Wrapper1。

RegistryProtocol export

流程與上面exportLocal中的部分類似,同樣通過Protoco$Adaptive進行export執行,會真正跳轉到InterfaceCompatibleRegistryProtocol(該類繼承自RegistryProtocol),因為此時url中的

protocol為registry

,url資訊表現為registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider…,在dubbo-registry-api中配置為registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol。

URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally,
URL providerUrl = getProviderUrl(originInvoker);
           

getRegistryUrl

:将registryUrl中的參數registry對應的實作設定為url的protocol即zookeeper

getProviderUrl

:拿到之前存在registryURL參數export中的provider url資訊,重新構造出provider URL

構造provider URL的OverrideListener

:把該url參數中的category置成了configurator,check設定為false,用于接受訂閱的服務方事件變更的通知

doLocalExport(originInvoker, providerUrl)

:由originalInvoker中的url資訊從目前緩存bounds中查找或建立一個exporter的包裝類ExporterChangeableWrapper。建立時現在InvokerDelegate中url的protocol是dubbo,是以會采用DubboProtocol的export産生一個exporter

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

DubboProtocol export:在構造DubboExporter的同時,還對url中server的類型選擇合适的Transporter實作(NettyTransporter等)建立一個可以通過ip port進行通信的server。

建立出來的DubboExporter将以service:port->exporter形式緩存到目前protocol的exportMap中;openServer會先從目前服務執行個體緩存(Map<String, ProtocolServer> serverMap,key為ip:port)查找,不存在則開始基于目前擴充配置的server協定建立server;

createServer:

private ProtocolServer createServer(URL url) {
 //為url中預設設定額外參數
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // enable heartbeat by default
                .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                .addParameter(CODEC_KEY, DubboCodec.NAME)
                .build();
// 将url中參數server的預設實作設為了Netty,去加載對應Transporter的實作
        String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
...

        ExchangeServer server = Exchangers.bind(url, requestHandler);
        
// 檢驗url中client的值(實際為null)是不是有對應加載的Transporter擴充
        str = url.getParameter(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 new DubboProtocolServer(server);
    }
           
  1. default URL:對url參數中添加預設資訊,并根據參數中server(預設netty)使用對應協定開啟服務,預設實作netty
  2. Exchangers bind:getExchanger(url).bind(url, handler),通過url參數中exchanger(預設header)加載對應的Exchanger SPI實作即HeaderExchanger;然後建立基于netty通信的exchange server,其中包含了傳輸層tcp協定的處理NettyTransporter,消息解碼器DecodeHandler,以及負責通過channel接收資料處理的HeaderExchangeHandler,它會将解析得到的請求交由真正負責處理的業務處理器handler即DubboProtocol中的ExchangeHandlerAdapter處理。
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
           
  1. wrap exporter:在得到ExchangeServer後,封裝為DubboProtocolServer并傳回

getRegistry(originInvoker)

:拿到invoker中的url作為registryUrl,通過injectExtension注入的registryFactory(目前RegistryProtocol中有RegistryFactory的setter方法)也是适應代理類,會找到ZookeeperRegistryFactory(繼承AbstractRegistryFactory)進行真正的注冊邏輯。

@Override
    public Registry getRegistry(URL url) {
    //去掉export參數(dubbo url資訊),覆寫interface為RegistryService
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
//生成的key為 zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService                
        String key = createRegistryCacheKey(url);
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            ```
            //省略了檢驗是否已加載或從緩存中擷取到的判斷代碼
            //create registry by spi/ioc
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }
           

createRegistry:ZookeeperRegistryFactory根據url和ZookeeperTransporter(适應代理類)建立一個ZookeeperRegistry,初始化要履歷的root node(/dubbo)資訊、相應的zkClient并添加zk的state listener,最後經過自動包裝成ListenerRegistryWrapper(可添加自定義的RegistryServiceListener實作做register和onUnregister的監聽邏輯)

registry.register(registeredProviderUrl)

:交由繼承了FailbackRegistry的ZookeeperRegistry進行注冊邏輯即在zk上建立對應的zk node。對原url進行category,service等封裝,最後在zk上建立父級持久節點**/dubbo/org.apache.dubbo.demo.DemoService/providers**,子臨時節點即目前服務執行個體的url資訊節點

/dubbo/org.apache.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F172.xx.xx.x%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-annotation-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%2CsayHelloAsync%26pid%3D12312%26release%3D%26service.name%3DServiceBean%3A%2Forg.apache.dubbo.demo.DemoService%26side%3Dprovider%26timestamp%3D1627702891449
@Override
    public void doRegister(URL url) {
        try {
        // url:dubbo://172.xx.xx.x:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=12312&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider&timestamp=1627702891449
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }
           

registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener)

:ZookeeperRegistry的doSubscribe會被調用,将對/dubbo/org.apache.dubbo.demo.DemoService/configurators目錄下添加ChildListener 實作動态配置的感覺。

該部分代碼針對指定了具體Interface的情況,任意interface在這裡不滿足是以不展開分析

CountDownLatch latch = new CountDownLatch(1);
                List<URL> urls = new ArrayList<>();
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                    ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));
                    if (zkListener instanceof RegistryChildListenerImpl) {
                        ((RegistryChildListenerImpl) zkListener).setLatch(latch);
                    }
                    zkClient.create(path, false);
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    ```
                }
                notify(url, listener, urls);
                // tells the listener to run only after the sync notification of main thread finishes.
                latch.countDown();
           

這裡的zkListener裡面存在一個之前添加的對provider URL的listener,當以現在的url通知listener時這裡就是RegistryProtocol$OverrideListener,如果得到有新的configurator資訊下的url會再次通過RegistryProtocol觸發reExport釋出新的服務方的url

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

notifyExport(exporter)

:對擴充的RegistryProtocolListener實作進行服務遠端注冊成功後的監聽邏輯調用,可以自定義擴充做事件的通知等

至此,服務DemoService在本地(injvm)及服務注冊中心zookeeper已經釋出完成。

exported

這裡的exportedURLs中隻有一個釋出到注冊中心的exporter,如果url的parameter中register-type是service的情況才會被認為将進行服務發現的處理,那麼這個資訊什麼時候可以被設定到url中呢?可以采用以下方式達成該條件

  • ServiceConfig中的doExportUrlsFor1Protocol時,如果配置的protocol為service-discovery-registry會設定registry-type為service。對于該demo項目可以在dubbo-provider.properties中設定

    dubbo.registry.protocol=service-discovery-registry

    ,同時也需要RegistryURL中有參數registry-type為service,配置為

    dubbo.registry.parameters[registry-type]=service

    ,最後exported中判斷isServiceDiscovery将為true

if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {

url = url.addParameterIfAbsent(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE);

}

此時registryURL将會是service-discovery-registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&id=org.apache.dubbo.config.RegistryConfig#0&pid=10736&registry=zookeeper&registry-type=service&registry.type=service&timestamp=1627742417006形式

  • 自定義ConfiguratorFactory讓dubbo能加載到,實作對url該資訊的特殊設定。比如自定義添加了一個ProviderConfiguratorFactory,因為此時的url protocol預設為dubbo在對應接口的配置檔案中key也需要定義為dubbo,ConfiguratorFactory是一個自适應方法會根據url中的protocol去選擇對應的實作。
Dubbo系列(二)服務釋出注冊原理前言服務釋出結語
  • 在dubbo-config-api目錄下,以NacosDubboServiceProviderBootstrap或ZookeeperDubboServiceProviderBootstrap等方式啟動服務,通過手動往url中添加該參數資訊

因為本次分析采用zookeeper預設注冊方式未有自定義registry-type,并不滿足是ServiceDiscovery的情況,是以下一步是針對自定義擴充等方式的分析。

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

service按key,group等進行具體配置中心的釋出處理

這裡又是一個SPI接口ServiceNameMapping 預設值為config,在不同module下有不同處理預設實作config,對應DynamicConfigurationServiceNameMapping

// dubbo-metadata-api配置實作

config=org.apache.dubbo.metadata.DynamicConfigurationServiceNameMapping

// dubbo-registry-api配置實作

metadata=org.apache.dubbo.registry.client.metadata.MetadataServiceNameMapping

DynamicConfigurationServiceNameMapping會對每個exportedURL進行map處理

map中會根據之前配置到environment中的動态配置類,進行服務到不同配置管理中心上的釋出比如Apollo,zk,Nacos等

具體實作如下

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

在ServiceDiscoveryRegistry中要訂閱的服務同時subscribe(URL,NotifyListener),完成了相應監聽器的設定

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

registerServiceInstancesChangedListener:其中調用具體ServiceDiscovery的注冊監聽邏輯。ZookeeperServiceDiscovery 為ServiceDiscovery擴充接口預設實作(zookeeper),

對于zk的服務變動事件(表現為節點資料及子節點的變化)而言,會通過watcher機制響應事件的處理。其中具體registerServiceWatcher建立對應path上的watcher監聽,

之後對每個service進行ServiceInstancesChangedListener的事件釋出監聽方法處理,包括更新目前服務清單,通過notifyListener通知其他監聽者

ZookeeperServiceDiscoveryChangeWatcher的process:

Dubbo系列(二)服務釋出注冊原理前言服務釋出結語

通過ServiceInstancesChangedListener會處理目前接收的事件ServiceInstancesChangedEvent,并繼續對該節點進行watch以接收下次事件變更。

發送服務釋出事件及監聽處理

// dispatch a ServiceConfigExportedEvent since 2.7.4
dispatch(new ServiceConfigExportedEvent(this));
           

這裡使用SPI 接口EventDispatcher(@SPI(DirectEventDispatcher.NAME)),預設采用DirectEventDispatcher(extends AbstractEventDispatcher)實作類釋出事件,直接使用簡單線程釋出一個ServiceConfigExportedEvent(類似也會有ServiceConfigUnexportedEvent)

而dubbo對該類事件也做出了擴充點SPI接口EventListener,可以自定義監聽器實作對服務釋出事件或取消釋出事件做出相應的處理。那麼這些監聽器是什麼時候被加載執行的呢?

前面的DirectEventDispatcher是繼承AbstractEventDispatcher,在dispatcher調用構造函數初始化的時候其中除了初始化Executor外,還有loadEventListenerInstances,找到所有配置的listener實作并添加到listenersCache中,内置的EventListener預設實作主要是列印資訊的簡單邏輯,如下

dubbo-registry目錄

registry-logging=org.apache.dubbo.registry.client.event.listener.LoggingEventListener

dubbo-config目錄

config-logging=org.apache.dubbo.config.event.listener.LoggingEventListener

publishExportEvent

與上述釋出不同的是,這裡是用spring的applicationContext将ServiceBeanExportedEvent事件釋出到spring容器中,需要在spring容器中有對應的監聽器bean處理。而上面的釋出是dubbo本身依靠dispatcher綁定EventListener機制,依賴自身的EventListener擴充加載實作并在dispatcher初始化時加載配置的listener,在dispatch的時候根據事件類型找到符合的listener進行onEvent的調用。

結語

此分析使用的版本為2.7.12-SNAPSHOT,距離現在dubbo github上釋出的版本也許不是最新,在一些代碼上有所差别但不影響主要流程的跟蹤。
           

通過dubbo源碼中提供的dubbo-demo子產品,用zk為注冊中心追溯了dubbo中一個服務釋出的具體過程,其中涉及到很多dubbo SPI的擴充類,為開發者提供了極大的可擴充性,以及加入緩存以增加對象複用性。其中在基于DubboProtocol建立server的部分涉及了netty相關通信細節等,都在dubbo中進行了很好的封裝對開發者進行了底層接口的隔離。

文章較長,後續有問題會再次進行更改補充~

繼續閱讀