Dubbo源碼分析-服務導出源碼解析(三)

在這個版本中dubbo會通過注解@PostConstructServiceBean實例放到ConfigManager

public abstract class AbstractConfig implements Serializable {
  @PostConstruct
    public void addIntoConfigManager() {
        ApplicationModel.getConfigManager().addConfig(this);
    }
......
}

DubboBootstrapApplicationListener 會監聽spring容器啟動發布的ContextRefreshedEvent,遍歷ConfigManager中的ServiceBean并調用export方法。

public class DubboBootstrapApplicationListener extends OnceApplicationContextEventListener implements Ordered {

    @Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (DubboBootstrapStartStopListenerSpringAdapter.applicationContext == null) {
            DubboBootstrapStartStopListenerSpringAdapter.applicationContext = event.getApplicationContext();
        }
        //spring容器啟動完事件
        if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }
..........省略代碼
}

調用ServiceBeanexport方法前,會初始化配置的參數,默認配置的優先級為:
JVM環境變量->操作系統環境變量->配置中心APP配置->配置中心Global配置->本地dubbo.properties文件配置中心配置->dubbo.properties文件配置。(這里還沒有涉及動態配置,在服務注冊后會監聽動態配置,這時候動態配置優先級最高)
最后根據配置文件的優先級對ServiceBean對中的屬性進行賦值。

執行到暴露服務的代碼前有較多與主流程不相關的邏輯,直接跳過。調用流程如下:

org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices
  --> org.apache.dubbo.config.bootstrap.DubboBootstrap#exportService
    --> org.apache.dubbo.config.ServiceConfig#export
      --> org.apache.dubbo.config.ServiceConfig#doExport
        --> org.apache.dubbo.config.ServiceConfig#doExportUrls
 private void doExportUrls() {
   .............省略部分代碼
        //獲得注冊中心地址
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

        int protocolConfigNum = protocols.size();
      // 根據協議循環注冊(dubbo,http)
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // In case user specified path, register service one more time to map it to path.
            repository.registerService(pathKey, interfaceClass);     
            doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
        }
    }

doExportUrls主要做兩點

  1. 獲取注冊中心地址。
  2. 根據協議注冊。

doExportUrlsFor1Protocol代碼比較多,我這里刪減了大部分代碼,只留下關鍵邏輯

 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
       .....省略
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
        .....省略
        exportLocal(url);
        .....省略
        if (CollectionUtils.isNotEmpty(registryURLs)) {
          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);
      }
  .....省略
}
  1. 根據拼接的map生成提供者的URL
  2. 進行本地暴露
  3. 進行遠程暴露

本地暴露

   private void exportLocal(URL url) {
        URL local = URLBuilder.from(url)
                 //替換protocol替換為injvm
                .setProtocol(LOCAL_PROTOCOL)
                //設置host為127.0.0.1
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        Exporter<?> exporter = PROTOCOL.export(
                PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
    }

exportLocal處理步驟為

  1. 替換protocol為injvm和host為127.0.0.1
  2. PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)生成invoker
    3.PROTOCOL.export()進行本地暴露

PROXY_FACTORYdubboAdaptive類,默認SPI擴展為javassist

 private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
public class JavassistProxyFactory extends AbstractProxyFactory {
。。。。。。。。。。。。省略
    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // 范圍Wrapper 的實現類
        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 { 
               //這里可以理解invokeMethod調用的是我們提供者具體的業務處理類。
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

入參proxyServiceBean中的ref,指向的是我們服務提供者的業務處理類(如demoServiceImpl)。getInvoker首先是動態生成Wrapper的實現類。然后返回一個AbstractProxyInvoker匿名內部類,
wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments)可以理解調用的具體的服務提供者業務處理類。

調用PROTOCOL.export()進行本地服務暴露。
PROTOCOL也是dubbo的Adaptiv類,因為protocolinjvm所以最終調用的是InjvmProtocol的export方法

private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
public class InjvmProtocol extends AbstractProtocol implements Protocol{

    public static final String NAME = LOCAL_PROTOCOL;

    public static final int DEFAULT_PORT = 0;
    private static InjvmProtocol INSTANCE;

    public InjvmProtocol() {
        INSTANCE = this;
    }

    public static InjvmProtocol getInjvmProtocol() {
  
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
       //本地暴露serviceKey就是接口的全限定類名。如org.apache.dubbo.demo.DemoService
        String serviceKey = invoker.getUrl().getServiceKey();
       // 將invoker ,serviceKey 封裝在  InjvmExporter 
        InjvmExporter<T> tInjvmExporter = new InjvmExporter<>(invoker, serviceKey, exporterMap);
       // 放入exporterMap
        exporterMap.addExportMap(serviceKey, tInjvmExporter);
        return tInjvmExporter;
    }
}

本地暴露的邏輯比較簡單serviceKey就是接口的全限定類名org.apache.dubbo.demo.DemoService
最后invoker被封裝在InjvmExporter,并serviceKey為key存儲在exporterMap

遠程暴露

遠程暴露與本地暴露一樣,調用的也是JavassistProxyFactorygetInvoker方法。但傳入的URL是注冊中心+服務提供者URL

 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
                                registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
 // DelegateProviderMetaDataInvoker也表示服務提供者,包括了Invoker和服務的配置
    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
   Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);

其中對應的各自SPI處理類如下

  • registry:// ---> RegistryProtocol
  • zookeeper:// ---> ZookeeperRegistry
  • dubbo:// ---> DubboProtocol

合并URL后先執行RegistryProtocolexport方法

public class RegistryProtocol implements Protocol {

..........省略
@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
      // registry://xxx?xx=xx&registry=zookeeper 轉換為 zookeeper://xxx?xx=xx     表示注冊中心
        URL registryUrl = getRegistryUrl(originInvoker);
        // 得到服務提供者url,表示服務提供者
        URL providerUrl = getProviderUrl(originInvoker);

       // overrideSubscribeUrl是老版本的動態配置監聽url,表示了需要監聽的服務以及監聽的類型(configurators, 這是老版本上的動態配置)
        // 在服務提供者url的基礎上,生成一個overrideSubscribeUrl,協議為provider://,增加參數category=configurators&check=false
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        // 一個overrideSubscribeUrl對應一個OverrideListener,用來監聽變化事件,監聽到overrideSubscribeUrl的變化后,
        // OverrideListener就會根據變化進行相應處理,具體處理邏輯看OverrideListener的實現
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

  // 在這個方法里會利用providerConfigurationListener和serviceConfigurationListener去重寫providerUrl
        // providerConfigurationListener表示應用級別的動態配置監聽器,providerConfigurationListener是RegistyProtocol的一個屬性
        // serviceConfigurationListener表示服務級別的動態配置監聽器,serviceConfigurationListener是在每暴露一個服務時就會生成一個
        // 這兩個監聽器都是新版本中的監聽器
        // 新版本監聽的zk路徑是:
        // 服務: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators節點的內容
        // 應用: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators節點的內容
        // 注意,要喝配置中心的路徑區分開來,配置中心的路徑是:
        // 應用:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService/dubbo.properties節點的內容
        // 全局:/dubbo/config/dubbo/dubbo.properties節點的內容
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
         // 根據動態配置重寫了providerUrl之后,就會調用DubboProtocol或HttpProtocol去進行導出服務了
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

        // // 得到注冊中心-ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
       //得到存入到注冊中心去的providerUrl,會對服務提供者url中的參數進行簡化
        final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

        //是否需要注冊到注冊中心
        boolean register = providerUrl.getParameter(REGISTER_KEY, true);
        if (register) {
           // 注冊服務,把簡化后的服務提供者url注冊到registryUrl中去
            registry.register(registeredProviderUrl);
        }

       // 針對老版本的動態配置,需要把overrideSubscribeListener綁定到overrideSubscribeUrl上去進行監聽
        registerStatedUrl(registryUrl, registeredProviderUrl, register);


        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);

        // Deprecated! Subscribe to override rules in 2.6.x or before.
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

        notifyExport(exporter);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<>(exporter);
    }
}

這是服務暴露比較重要分方法,主要步驟如下
1.獲取注冊中心的URL,并把 registry://xxx?xx=xx&registry=zookeeper 轉換為 zookeeper://xxx?xx=xx 表示注冊中心

  1. 得到服務提供者url,表示服務提供者。
  2. 構建老板本dubbo監聽的URL. overrideSubscribeUrl是老版本的動態配置監聽url,表示了需要監聽的服務以及監聽的類型(configurators, 這是老版本上的動態配置)
  3. 監聽動態配置,其中動態配置分為應用動態配置和服務動態配置并重寫providerUrl。(注意不是配置中心)
    服務配置路徑: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators
    應用配置路徑: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators
  4. 根據動態配置重寫了providerUrl之后,就會調用DubboProtocolHttpProtocol去進行導出服務了
  5. 得到注冊中心實現-ZookeeperRegistry
  6. 注冊服務,把簡化后的服務提供者url注冊到registryUrl中去
  7. 得到存入到注冊中心去的providerUrl,會對服務提供者url中的參數進行簡化
  8. 針對老版本的動態配置,需要把overrideSubscribeListener綁定到overrideSubscribeUrl上去進行監聽

監聽動態配置和重寫提供者URL

private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) {
        // 應用配置,providerConfigurationListener是在屬性那里直接初始化好的,providerConfigurationListener會監聽配置中心的應用配置信息變動
        providerUrl = providerConfigurationListener.overrideUrl(providerUrl);
        // 服務配置,new ServiceConfigurationListener的時候回初始化,ServiceConfigurationListener會監聽配置中心的服務信息配置信息變動
        ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener);
        serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener);
        return serviceConfigurationListener.overrideUrl(providerUrl);
    }

應用動態配置由 ProviderConfigurationListener 實現,它是RegistryProtocol的一個成員變量,在RegistryProtocol實例化是同時也實例化ProviderConfigurationListener,在ProviderConfigurationListener 的構造函數中調用initWith方法獲取應用動態配置(initWith在AbstractConfiguratorListener類構造方法中調用,AbstractConfiguratorListener也是ServiceConfigurationListener的父類,所以服務監聽實例化時也會調用這個方法獲取服務動態配置)。

 protected final void initWith(String key) {
        // 添加Listener,進行了訂閱
        ruleRepository.addListener(key, this);
        // 從配置中心ConfigCenter獲取屬于當前應用的動態配置數據,從zk中拿到原始數據(主動從配置中心獲取數據)
        String rawConfig = ruleRepository.getRule(key, DynamicConfiguration.DEFAULT_GROUP);
        if (!StringUtils.isEmpty(rawConfig)) {
            // 如果存在應用配置信息則根據配置信息生成Configurator
            genConfiguratorsFromRawRule(rawConfig);
        }
    }

overrideUrlWithConfig方法中首先根據應用動態配置調用overrideUrl方法重寫提供者URL,再實例化ServiceConfigurationListener獲取服務動態配置,調用overrideUrl方法重寫提供者URL。

服務導出

doLocalExport方法中,會調用protocol.export()服務導出,默認使用的是DubboProtocol

public class DubboProtocol extends AbstractProtocol {
 @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        String key = serviceKey(url);
        // 構造一個Exporter進行服務導出
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.addExportMap(key, exporter);

       ......省略
        // 開啟NettyServer
        openServer(url);
        // 特殊的一些序列化機制,比如kryo提供了注冊機制來注冊類,提高序列化和反序列化的速度
        optimizeSerialization(url);

        return exporter;
    }
}

把傳入的Invoker封裝成DubboExporter放入到exporterMap中。然后開啟Netty Server。
開啟netty流程不在贅述了,有興趣可以執行閱讀,流程如下。

org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
  -->org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#createServer
    --> org.apache.dubbo.remoting.exchange.Exchangers#bind
      --> org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
        --> org.apache.dubbo.remoting.Transporters#bind
          --> org.apache.dubbo.remoting.transport.netty.NettyTransporter#bind
           --> org.apache.dubbo.remoting.transport.netty.NettyServer
             --> org.apache.dubbo.remoting.transport.netty.NettyServer#doOpen

服務注冊

服務注冊根據不同的注冊中心有不同實現,ZK實現為ZookeeperRegistry,所以 registry.register(registeredProviderUrl)方法最終會調用到ZookeeperRegistrydoRegister方法。

  public void doRegister(URL url) {
        try {
            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);
        }
    }

至此,服務導出源碼解析的源碼就分析完了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容