超详细Dubbo服务导出源码解读

1. Dubbo服务导出概述

前面已经解读过Dubbo SPI相关的源码见:一篇短文就能搞定Dubbo SPI 源码及示例。本文主要研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。本文将基于dubbo-2.7.7源码对这三个部分代码进行详细的分析。

2. Dubbo服务导出源码解读

2.1 服务导出入口

在2.7.5之前服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。但是在2.7.5开始新增了DubboBootstrapApplicationListener,该类继承了OneTimeExecutionApplicationContextEventListener抽象类,OneTimeExecutionApplicationContextEventListener又实现了ApplicationListener接口onApplicationEvent方法,同之前版本一样,该方法会在收到 Spring 上下文刷新事件后执行。两个类的关键源码如下:

abstract class OneTimeExecutionApplicationContextEventListener implements ApplicationListener, ApplicationContextAware {

    private ApplicationContext applicationContext;

    public final void onApplicationEvent(ApplicationEvent event) {
    	//判断事件源是持有的ApplicationContext并且是应用上下文事件
        if (isOriginalEventSource(event) && event instanceof ApplicationContextEvent) {
            onApplicationContextEvent((ApplicationContextEvent) event);
        }
    }

    /**
     * The subclass overrides this method to handle {@link ApplicationContextEvent}
     *
     * @param event {@link ApplicationContextEvent}
     */
    protected abstract void onApplicationContextEvent(ApplicationContextEvent event);
 //省略部分源码
}

public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener
        implements Ordered {

    /**
     * The bean name of {@link DubboBootstrapApplicationListener}
     */
    public static final String BEAN_NAME = "dubboBootstrapApplicationListener";

    private final DubboBootstrap dubboBootstrap;

    public DubboBootstrapApplicationListener() {
        this.dubboBootstrap = DubboBootstrap.getInstance();
    }
    
    @Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent) {//是上下文刷新事件则调用DubboBootstrap的start方法
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {//如果是上下文关闭事件则调用DubboBootstrap的stop方法
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        dubboBootstrap.start();
    }

    private void onContextClosedEvent(ContextClosedEvent event) {
        dubboBootstrap.stop();
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}

2.2 DubboBootstrap#start

根据上面的源码可知,在DubboBootstrapApplicationListener 的构造函数中会先去获取DubboBootstrap 的实例,在监听到ContextRefreshedEvent事件时触发DubboBootstrap 的start方法,因此接着我们看看DubboBootstrap 的实例化及开始方法的源码。

DubboBootstrap 实例化

public class DubboBootstrap extends GenericEventListener {


    private static DubboBootstrap instance;

    private final ConfigManager configManager;

    private final Environment environment;

    /**
     * 加锁构造单例
     * See {@link ApplicationModel} and {@link ExtensionLoader} for why DubboBootstrap is designed to be singleton.
     */
    public static synchronized DubboBootstrap getInstance() {
        if (instance == null) {
            instance = new DubboBootstrap();
        }
        return instance;
    }
    //私有化构造函数,保证只能被自己实例化
    private DubboBootstrap() {
    	//通过SPI方式获取环境配置和环境管理实例,继承关系如下图
        configManager = ApplicationModel.getConfigManager();
        environment = ApplicationModel.getEnvironment();
        //注册shutdown事件,回调DubboBootstrap的destroy方法,销毁所有导出及引用的服务等
        DubboShutdownHook.getDubboShutdownHook().register();
        ShutdownHookCallbacks.INSTANCE.addCallback(new ShutdownHookCallback() {
            @Override
            public void callback() throws Throwable {
                DubboBootstrap.this.destroy();
            }
        });
    }

在这里插入图片描述

org.apache.dubbo.config.bootstrap.DubboBootstrap.start()

  /**
     * Start the bootstrap
     */
    public DubboBootstrap start() {
    	//原子操作,保证只启动一次
        if (started.compareAndSet(false, true)) {
        	//初始化操作
            initialize();
            if (logger.isInfoEnabled()) {
                logger.info(NAME + " is starting...");
            }
            // 导出dubbo服务(最终调用ServiceConfig的export方法)
            exportServices();

            // 不仅仅是注册服务提供者或者已经导出元数据
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 导出元数据服务(最终构建了ServiceConfig实例,然后调用export方法)
                exportMetadataService();
                // 如果有则需要注册本地服务实例,通过SPI方式获取服务发现注册中心,然后调用他们的注册方法(默认自适应拓展实现是zookeeper)
                registerServiceInstance();
            }
            //执行服务引入
            referServices();

            if (logger.isInfoEnabled()) {
                logger.info(NAME + " has started.");
            }
        }
        return this;
    }
2.3 DubboBootstrap#start内部

根据上面start方法源码可以看到,里面一次执行了服务初始化以服务导出和引入的方法,接下来我们追踪下个方法的具体实现。

org.apache.dubbo.config.bootstrap.DubboBootstrap.initialize()

/**
     * Initialize
     */
    private void initialize() {
    	// 原子操作确保只初始化一次
        if (!initialized.compareAndSet(false, true)) {
            return;
        }
        //初始化Dubbo组件的生命周期,这里主要是对环境配置初始化
        ApplicationModel.iniFrameworkExts();
        //开始构建配置中心
        startConfigCenter();
        //如果是zookeeper作为注册中心且没有指定配置中心时,使用注册中心做配置中心
        useRegistryAsConfigCenterIfNecessary();
        //加载协议ID到协议配置中并加载注册id到注册中心配置
        loadRemoteConfigs();
        //全局配置校验(应用、元数据、提供者、消费者、监控等)
        checkGlobalConfigs();
        //初始化元数据服务
        initMetadataService();
        //初始化事件监听器(将当前实例添加到事件监听器中)
        initEventListener();

        if (logger.isInfoEnabled()) {
            logger.info(NAME + " has been initialized!");
        }
    }

org.apache.dubbo.config.bootstrap.DubboBootstrap.exportServices()

 private void exportServices() {
        configManager.getServices().forEach(sc -> {
            //设置ServiceConfig的启动器为当前实例
            ServiceConfig serviceConfig = (ServiceConfig) sc;
            serviceConfig.setBootstrap(this);
            
            if (exportAsync) {
            	//异步导出,将导出任务提交到线程池异步完成
                ExecutorService executor = executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    sc.export();
                });
                asyncExportingFutures.add(future);
            } else {
            	//同步导出 则直接调用ServiceConfig的导出方法
                sc.export();
                exportedServices.add(sc);
            }
        });
    }

org.apache.dubbo.config.bootstrap.DubboBootstrap.registerServiceInstance()

private void registerServiceInstance() {
    	//判断是否有ServiceDiscoveryRegistry,有则通过他获取ServiceDiscovery放入集合中
        if (CollectionUtils.isEmpty(getServiceDiscoveries())) {
        	//没有则直接返回
            return;
        }
        // 获取应用配置
        ApplicationConfig application = getApplication();
        //从配置中获取服务名称
        String serviceName = application.getName();
        //获取元数据导出地址
        URL exportedURL = selectMetadataServiceExportedURL();
        //获取主机
        String host = exportedURL.getHost();
        //获取端口
        int port = exportedURL.getPort();
        //根据名称主机和端口创建DefaultServiceInstance实例
        ServiceInstance serviceInstance = createServiceInstance(serviceName, host, port);
        //获取服务发现中心调用注册方法,注册服务实例
        getServiceDiscoveries().forEach(serviceDiscovery -> serviceDiscovery.register(serviceInstance));
    }

在这里插入图片描述

2.4 ServiceConfig#export

根据上面源码分析可知,导出服务方法最终都会调用ServiceConfig的export方法进行导出,接下来将进入这个方法源码分析。

 public synchronized void export() {
    	//判断当前服务是否需要导出
        if (!shouldExport()) {
            return;
        }
        //启动类为空则获取一个实例
        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            bootstrap.init();
        }
        //校验并更细配置(默认配置、协议配置等)
        checkAndUpdateSubConfigs();

        //初始化元数据(设置版本、分组、类型及名称等属性)
        serviceMetadata.setVersion(version);
        serviceMetadata.setGroup(group);
        serviceMetadata.setDefaultGroup(group);
        serviceMetadata.setServiceType(getInterfaceClass());
        serviceMetadata.setServiceInterfaceName(getInterface());
        serviceMetadata.setTarget(getRef());
        //是否需要延迟导出
        if (shouldDelay()) {
        	//提交导出任务到延迟导出调度器(可调度线程池)
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
        	//执行导出操作
            doExport();
        }
        //执行已导出操作
        exported();
    }

    public void exported() {
        // 发布一个服务导出事件( ServiceConfigExportedEvent)
        dispatch(new ServiceConfigExportedEvent(this));
    }
2.5 ServiceConfig#checkAndUpdateSubConfigs

上一步导出源码可知,除了一些初始化及延迟导出的校验外,还有两个主要的逻辑:在执行导出前的配置校验更新操作;执行导出逻辑。本节首先分析配置校验部分的源码,然后分析执行导出源码。

org.apache.dubbo.config.ServiceConfig.checkAndUpdateSubConfigs()

private void checkAndUpdateSubConfigs() {
        // 完成组件注册(协议、注册中心)
        completeCompoundConfigs();
        //检查provider是否为空,为空则获取默认默认的provider,默认的不存在则新建一个ProviderConfig实例
        checkDefault();
        //检查协议,如果协议为空并且provider不为空,则获取provider中的协议;将根据协议ID转换协议
        checkProtocol();
        // 初始化一些为空的配置
        List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class)
                .getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
        configInitializers.forEach(e -> e.initServiceConfig(this));

        // 如果协议不是仅仅是injvm即需要导出服务给外部使用,则校验注册中心
        if (!isOnlyInJvm()) {
        	//检验注册中心配置是否存在,然后通过id转换成注册中心配置RegistryConfig
        	// 转换完成后遍历所有Registry是否可用(注册地址是否为空),不可用则会抛出异常
            checkRegistry();
        }
        //调用刷新配置方法
        //1. 首先调用org.apache.dubbo.common.config.Environment.getPrefixedConfiguration(AbstractConfig)方法,
        //该方法会从多种配置源(AbstractConfig (API, XML, annotation), - D, config center)中找出Application, Registry, Protocol等的最优配置
        //2.通过getClass().getMethods()获取所有的方法,判断是否是setter或者setParameters方法,如果是就通过反射调用将将新的配置设置进去
        this.refresh();
        //接口名称为空,抛出异常
        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        //检查引用实现是否是泛化服务类型
        if (ref instanceof GenericService) {
        	//设置接口类为泛化服务类
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
            	//泛化服务标识符为空则设置为true
                generic = Boolean.TRUE.toString();
            }
        } else {//不是泛化服务类型
            try {
            	//接口类通过接口名加载
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //检查远程服务接口和方法是否符合Dubbo的要求,主要检查配置文件中配置的方法是否包含在远程服务接口中
            checkInterfaceAndMethods(interfaceClass, getMethods());
            //检查引用不应为空,并且是给定接口的实现
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        //服务接口的本地实现类名不为空
        if (local != null) {
        	//如果local不是类名而是true,则拼接类名
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
            	//根据名称使用线程上下文类加载器加载本地类
                localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //如果本地类没有实现指定接口,则抛出异常
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        //本地存根和上面的local逻辑一致
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        //本地存根合法性校验,主要也是校验了是否是之指定接口的实现
        checkStubAndLocal(interfaceClass);
        //Mock校验,主要校验值mock值是否合法及mock类是否合法(实现指定接口并且有默认构造函数)
        ConfigValidationUtils.checkMock(interfaceClass, this);
        //服务配置校验(版本路径票据、扩展校验(ExporterListener、ProxyFactory、InvokerListener、Cluster等)、注册中心、协议即提供者校验)
        ConfigValidationUtils.validateServiceConfig(this);
        //配置后置处理器调用ConfigPostProcessor#postProcessServiceConfig方法
        postProcessConfig();
    }

org.apache.dubbo.config.ServiceConfig.doExport()

protected synchronized void doExport() {
    	//是否执行了unexport方法(表示服务注销了),执行了则抛出异常
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        //已经导出过则直接返回
        if (exported) {
            return;
        }
        //设置已导出标志
        exported = true;
        //路径及服务名为空
        if (StringUtils.isEmpty(path)) {
        	//服务名为接口名
            path = interfaceName;
        }
        //执行多协议多注册导出
        doExportUrls();
    }
2.6 多协议多注册中心导出ServiceConfig#doExportUrls

根据上一步中的导出逻辑可知,最后调用了doExportUrls方法,该方法对dubbo多协议,多注册中心进行了支持,源码如分析如下:

 private void doExportUrls() {
    	//获取服务缓存库
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        //注册当前服务到本地缓存库
        ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
        //注册服务提供者到缓存库
        repository.registerProvider(
        		//根据接口名,服务组及版本号生成唯一名称
                getUniqueServiceName(),
                ref,
                serviceDescriptor,
                this,
                serviceMetadata
        );
        //加载注册中心地址(支持多注册中心,因此是集合)
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
        
        for (ProtocolConfig protocolConfig : protocols) {//支持多协议导出
        	//根据协议配置生成服务地址(注册中心的key)
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // 如果指定了服务路径,需要再次注册到缓存中,保证此映射路径能获取到服务
            repository.registerService(pathKey, interfaceClass);
            // 设置原数据服务key
            serviceMetadata.setServiceKey(pathKey);
            //执行多中心单协议的导出逻辑
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

通过上面的源码分析可知,多协议导出首次调用ConfigValidationUtils#loadRegistries方法加载多注册中心,然后遍历每个协议执行该协议的导出逻辑。

public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
		// check && override if necessary
		List<URL> registryList = new ArrayList<URL>();
		// 获取服务应用配置
		ApplicationConfig application = interfaceConfig.getApplication();
		// 获取服务注册配置
		List<RegistryConfig> registries = interfaceConfig.getRegistries();
		if (CollectionUtils.isNotEmpty(registries)) {// 注册配置不为空
			for (RegistryConfig config : registries) {// 遍历注册配置
				// 获取注册地址
				String address = config.getAddress();
				if (StringUtils.isEmpty(address)) {
					// 如果为设置默认值0.0.0.0
					address = ANYHOST_VALUE;
				}
				// 如果地址不是N/A
				if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
					Map<String, String> map = new HashMap<String, String>();
					// 通过getter或者getParameters方法将ApplicationConfig和RegistryConfig属性放到map中
					AbstractConfig.appendParameters(map, application);
					AbstractConfig.appendParameters(map, config);
					// <path,org.apache.dubbo.registry.RegistryService>存入map
					map.put(PATH_KEY, RegistryService.class.getName());
					// 添加dubbo、版本、时间戳等参数到map中
					AbstractInterfaceConfig.appendRuntimeParameters(map);
					// 参数中是否包含协议protocol,如果没有设置为dubbo
					if (!map.containsKey(PROTOCOL_KEY)) {
						map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
					}
					// 根据参数和地址解析URL
					List<URL> urls = UrlUtils.parseURLs(address, map);

					for (URL url : urls) {
						// 根据添加registry参数组装注册中心地址
						url = URLBuilder.from(url).addParameter(REGISTRY_KEY, url.getProtocol())
								.setProtocol(extractRegistryType(url)).build();
						// 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:
						// (服务提供者 && register = true 或 null) || (非服务提供者 && subscribe = true 或 null)
						if ((provider && url.getParameter(REGISTER_KEY, true))
								|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
							registryList.add(url);
						}
					}
				}
			}
		}
		return registryList;
	}
2.7 单协议多注册中心导出ServiceConfig#doExportUrlsFor1Protocol

有多协议多注册中心源码分析可知,最终调用了但协议多注册中心导出方法,本方法的源码比较长,主要逻辑就是导出前先获取了各种配置缓存起来,然后获取创建包装实例、获取主机和端口,最后执行导出。导出根据导出范围分为三个分支:scope = none,不导出服务;scope != remote,导出到本地;scope != local,导出到远程。

	private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
		String name = protocolConfig.getName();
		// 协议名称为空,设置为dubbo
		if (StringUtils.isEmpty(name)) {
			name = DUBBO;
		}
		// 添加配置<side,provider>
		Map<String, String> map = new HashMap<String, String>();
		map.put(SIDE_KEY, PROVIDER_SIDE);
		// 添加dubbo、版本、时间戳等运行参数到map中
		ServiceConfig.appendRuntimeParameters(map);
		// 通过getter或者getParameters方法将ApplicationConfig、ModuleConfig、MetricsConfig、
		// ProviderConfig、ProtocolConfig及ServiceConfig属性放到map中
		AbstractConfig.appendParameters(map, getMetrics());
		AbstractConfig.appendParameters(map, getApplication());
		AbstractConfig.appendParameters(map, provider);
		AbstractConfig.appendParameters(map, protocolConfig);
		AbstractConfig.appendParameters(map, this);
		// 获取原数导出配置,如果合法则添加<metadata,remote>到map中
		MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
		if (metadataReportConfig != null && metadataReportConfig.isValid()) {
			map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
		}
		//是否存在 <dubbo:method> 标签的配置信息
		if (CollectionUtils.isNotEmpty(getMethods())) {
			for (MethodConfig method : getMethods()) {
				// 通过getter或者getParameters方法将MethodConfig属性添加到map中,前缀是当前MethodConfig名称即<方法名.属性名,属性值>。
				AbstractConfig.appendParameters(map, method, method.getName());
				String retryKey = method.getName() + ".retry";
				//是否存在methodname.retry键,及是否配置了retry属性
				if (map.containsKey(retryKey)) {
					//存在则移除
					String retryValue = map.remove(retryKey);
					//如果retry配置的false,设置retries配置为0
					if ("false".equals(retryValue)) {
						map.put(method.getName() + ".retries", "0");
					}
				}
				//获取方法参数配置ArgumentConfig,存放到map中
				List<ArgumentConfig> arguments = method.getArguments();
				if (CollectionUtils.isNotEmpty(arguments)) {
					for (ArgumentConfig argument : arguments) {
						// 判断参数类型是否为空
						if (argument.getType() != null && argument.getType().length() > 0) {
							//获取接口(导出服务)的所有方法,遍历
							Method[] methods = interfaceClass.getMethods();
							if (methods.length > 0) {
								for (int i = 0; i < methods.length; i++) {
									String methodName = methods[i].getName();
									// 接口方法名和方法配置名称相同
									if (methodName.equals(method.getName())) {
										//获取接口方法参数类型
										Class<?>[] argtypes = methods[i].getParameterTypes();
										// 参数索引不是-1,-1表示未设置
										if (argument.getIndex() != -1) {
											 // 检测 ArgumentConfig 中的 type 属性与方法参数列表中的参数名称是否一致
											if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
												//一致的则添加配置到map<方法名.参数索引,参数配置>
												AbstractConfig.appendParameters(map, argument,
														method.getName() + "." + argument.getIndex());
											} else {//不一致则抛出异常
												throw new IllegalArgumentException(
														"Argument config error : the index attribute and type attribute not match :index :"
																+ argument.getIndex() + ", type:" + argument.getType());
											}
										} else {//未设置参数索引
											// 遍历方法参数的所有类型
											for (int j = 0; j < argtypes.length; j++) {
												Class<?> argclazz = argtypes[j];
												//查找和当前参数配置类型匹配的参数
												if (argclazz.getName().equals(argument.getType())) {
													//匹配则将配置放入map中<方法名.参数索引,参数配置>
													AbstractConfig.appendParameters(map, argument,
															method.getName() + "." + j);
													//如果匹配到的参数类型设置了索引并且和当前索引不一致,抛出异常
													if (argument.getIndex() != -1 && argument.getIndex() != j) {
														throw new IllegalArgumentException(
																"Argument config error : the index attribute and type attribute not match :index :"
																		+ argument.getIndex() + ", type:"
																		+ argument.getType());
													}
												}
											}
										}
									}
								}
							}
						} else if (argument.getIndex() != -1) {
							//参数类型为空但是参数索引不是-1,添加配置到map中
							AbstractConfig.appendParameters(map, argument,
									method.getName() + "." + argument.getIndex());
						} else {
							//如果既没有配置参数索引又没哟配置参数类型则抛出异常
							throw new IllegalArgumentException(
									"Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
						}

					}
				}
			} 
		}
		
		if (ProtocolUtils.isGeneric(generic)) {
			//是泛化服务则设置<generic,generic值>、<methods,*>表示任意方法
			map.put(GENERIC_KEY, generic);
			map.put(METHODS_KEY, ANY_VALUE);
		} else {
			//不是泛化服务,获取修订版本号,放入map
			String revision = Version.getVersion(interfaceClass, version);
			if (revision != null && revision.length() > 0) {
				map.put(REVISION_KEY, revision);
			}
			//创建并返回包装类方法名称
			String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
			if (methods.length == 0) {
				//没有包装方法名则设置<methods,*>
				logger.warn("No method found in service interface " + interfaceClass.getName());
				map.put(METHODS_KEY, ANY_VALUE);
			} else {
				//如果有,则方面通过逗号分隔拼接放入map中,key=methods
				map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
			}
		}

		//没有token且提供者不为空,获取提供者的token
		if (ConfigUtils.isEmpty(token) && provider != null) {
			token = provider.getToken();
		}
		//如果token不为空
		if (!ConfigUtils.isEmpty(token)) {
			//如果token是默认值(true或者default),则创建UUID作为token
			if (ConfigUtils.isDefault(token)) {
				map.put(TOKEN_KEY, UUID.randomUUID().toString());
			} else {
				map.put(TOKEN_KEY, token);
			}
		}
		// init serviceMetadata attachments
		serviceMetadata.getAttachments().putAll(map);

		// 找到配置的主机,优先级如下:environment variables -> java system properties -> host property in config file 
		// -> /etc/hosts -> default network address -> first available network address
		String host = findConfigedHosts(protocolConfig, registryURLs, map);
		// 找到配置的端口,优先级: environment variable -> java system properties ->
		// port property in protocol config file -> protocol default port
		Integer port = findConfigedPorts(protocolConfig, name, map);
		//拼接URL
		URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

		// 如果存在则获取自定义扩展配置
		if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).hasExtension(url.getProtocol())) {
			url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getExtension(url.getProtocol())
					.getConfigurator(url).configure(url);
		}
		//获取导出范围
		String scope = url.getParameter(SCOPE_KEY);
		// 如果导出单位不是none(none则不执行导出)
		if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

			// 如果导出范围不是remote则执行本地导出逻辑
			if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
				exportLocal(url);
			}
			// 如果配置导出范围不是local则执行远程导出
			if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
				//注册中心地址不为空
				if (CollectionUtils.isNotEmpty(registryURLs)) {
					for (URL registryURL : registryURLs) {
						// 如果协议是injvm 则不执行导出注册逻辑
						if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
							continue;
						}
						//url配置dynamic参数
						url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
						//获取监控地址
						URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
						if (monitorUrl != null) {
							//监控地址不为空则添加到URL参数中key=monitor
							url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
						}
						if (logger.isInfoEnabled()) {
							if (url.getParameter(REGISTER_KEY, true)) {
								logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url
										+ " to registry " + registryURL);
							} else {
								logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
							}
						}

						// 获取自定义代理配置
						String proxy = url.getParameter(PROXY_KEY);
						if (StringUtils.isNotEmpty(proxy)) {
							//如果存在,则为注册地址添加代理实现参数
							registryURL = registryURL.addParameter(PROXY_KEY, proxy);
						}
						//为服务引用生成Invoker对象
						Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
								registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
						//生成提供者和配置包装Invoker
						DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker,
								this);
						//通过SPI自适应拓展获取Protocol的拓展实现,调用导出方法
						Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
						//添加到导出器缓存
						exporters.add(exporter);
					}
				} else {
					//注册中心地址为空,导出服务到配置地址
					if (logger.isInfoEnabled()) {
						logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
					}
					Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
					DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

					Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
					exporters.add(exporter);
				}
				/**
				 * @since 2.7.0 ServiceData Store
				 * 获取可写元数据服务,默认实现为本地
				 */
				WritableMetadataService metadataService = WritableMetadataService
						.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
				if (metadataService != null) {
					//发布服务定义
					metadataService.publishServiceDefinition(url);
				}
			}
		}
		//添加到服务引用url缓存中
		this.urls.add(url);
	}
发布了131 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/105357071