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);
        }
    }

至此,服务导出源码解析的源码就分析完了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容