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);
  • .注册中心URL: registry://127.0.0.1:2181/xxxxxxxxxx
  • 服务提供者URL : dubbo://192.168.2.19:20880/xxxxxxxxxx
  • 调用 registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())合并后的URL。export后面的是拼装了服务提供者URL部分。registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&
    export=dubbo%3A%2F%2F192.168.2.19%3A20880%xxxxxxxxxx

其中对应的各自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 表示注册中心
2. 得到服务提供者url,表示服务提供者。
3. 构建老板本dubbo监听的URL. overrideSubscribeUrl是老版本的动态配置监听url,表示了需要监听的服务以及监听的类型(configurators, 这是老版本上的动态配置)
4. 监听动态配置,其中动态配置分为应用动态配置和服务动态配置并重写providerUrl。(注意不是配置中心)
服务配置路径: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators
应用配置路径: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators
5. 根据动态配置重写了providerUrl之后,就会调用DubboProtocolHttpProtocol去进行导出服务了
6. 得到注册中心实现-ZookeeperRegistry
7. 注册服务,把简化后的服务提供者url注册到registryUrl中去
8. 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化
9. 针对老版本的动态配置,需要把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);
        }
    }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值