apollo客户端的启动流程源码分析

目录

前言

一、apollo客户端启动完成的工作

二、apollo配置加载的过程

1.引入库

2.initialize()

 3.initializ(environment)

4.postProcessEnvironment()

三、springboot配置加载的流程

四、springboot和apollo属性加载的先后顺序

总结


前言

公司中使用apollo作为配置中心,最近时间比较宽裕,便决定把apollo源码学习一下,本文主要是客户端启动的相关源码。

一、apollo客户端启动完成的工作

客户端启动过程中,主要完成了读取apollo中的配置和启动基于长轮询的实时更新任务两个任务,下文主要在源码层次上解析加载apollo配置的过程,关于长轮询逻辑会在以后的文章中单独写出。

二、apollo配置加载的过程

1.引入库

apollo客户端的启动过程主要是通过ApolloApplicationContextInitializer这个类实现的,实现了EnvironmentPostProcessor和ApplicationContextInitializer接口,主要通过initialize和postProcessEnvironment两个方法继续进行,下面将分别针对两个方法进行分析。

2.initialize()

 在initialize方法中又再次调用了自己的initialize(environment)方法,这个方法作为apollo的初始化方法,在postProcessEnvironment方法中也有调用,通过上面的调用链可以看到,initialize方法通过BootstrapApplicationListener调用到,当前还没有加载application.properties文件中的属性,具体原因会在后面进行分析,所以需要在系统属性或者环境变量中配置apollo.bootstrap.enable=true,才能在这生效,执行下面的initialize(environment)方法。

  public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();
    //环境中是否配置apollo.bootstrap.enabled=true,由于
    String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
    if (!Boolean.valueOf(enabled)) {
      logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
      return;
    }
    logger.debug("Apollo bootstrap config is enabled for context {}", context);

    initialize(environment);
  }

 3.initializ(environment)

方法源码如下所示,其中比较重要的方法是ConfigService.getConfig(namespace),该方法读取了apollo配置,并构建PropertySource,注入到容器中。

  protected void initialize(ConfigurableEnvironment environment) {

    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }

    String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
    logger.debug("Apollo bootstrap namespaces: {}", namespaces);
    List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

    CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    for (String namespace : namespaceList) {
      //获取apollo配置
      Config config = ConfigService.getConfig(namespace);
      //将属性添加到composite中
      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }
    //将apollo属性的优先级设为最高
    environment.getPropertySources().addFirst(composite);
  }

 getConfig方法的调用链比较长,下面是几个关键点,源码如下。

  LocalFileConfigRepository createLocalConfigRepository(String namespace) {
    if (m_configUtil.isInLocalMode()) {
      logger.warn(
          "==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
          namespace);
      return new LocalFileConfigRepository(namespace);
    }
    //先读取远程服务器配置,在读取本地缓存文件配置
    return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
  }

 RemoteConfigRepository对象的构造方法中分别实现了远程获取配置,开启长轮询任务和定时更新任务

  public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;
    m_configCache = new AtomicReference<>();
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
    remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
    m_longPollServiceDto = new AtomicReference<>();
    m_remoteMessages = new AtomicReference<>();
    m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
    m_configNeedForceRefresh = new AtomicBoolean(true);
    m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
        m_configUtil.getOnErrorRetryInterval() * 8);
    gson = new Gson();
    //远程获取配置
    this.trySync();
    //开启定时更新任务
    this.schedulePeriodicRefresh();
    //开启长轮询任务
    this.scheduleLongPollingRefresh();
  }

 LocalFileConfigRepository对象的构造方法中,在无法从远程获取配置的情况下,会从本地配置文件中读取配置。

  public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
    m_namespace = namespace;
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    this.setLocalCacheDir(findLocalCacheDir(), false);
    this.setUpstreamRepository(upstream);
    //从本地缓存中获取配置
    this.trySync();
  }

4.postProcessEnvironment()

 方法的作用与initialize基本一致,不做过多解释,区别在于在哪个阶段执行。

 public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {

    // should always initialize system properties like app.id in the first place
    initializeSystemProperty(configurableEnvironment);
    //获取apollo.bootstrap.eagerLoad.enabled配置属性的值
    Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);

    //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
    if (!eagerLoadEnabled) {
      return;
    }
    //获取apollo.bootstrap.enabled配置属性的值
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);

    if (bootstrapEnabled) {
      initialize(configurableEnvironment);
    }
  }

三、springboot配置加载的流程

springboot中配置属性的加载主要是在preperEnvironment方法中实现的,源码如下。

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//创建环境,导入servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//注入commonLine配置源,并提高有限级到最高。
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		//发布事件,完成properties文件的加载,apollo也在此处生成propertySource写入环境中,在properties文件中配置apollo.bootstrap.enabled=true
		//在init方法中由于没有加而不会生效
		//导入applicationConfig: [classpath:/application.properties](默认properties文件)
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

在getOrCreateEnvironment()方法中完成了对servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment属性的加载,重点代码如下:

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

在listeners.environmentPrepared方法中完成了对默认文件属性的加载,主要是ConfigFileApplicationListener中的postProcessEnvironment方法实现的,重点代码如下:

		public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		    addPropertySources(environment, application.getResourceLoader());
	}
		protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		    RandomValuePropertySource.addToEnvironment(environment);
		    new Loader(environment, resourceLoader).load();
	}
void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
						//加载用户配置的文件
						initializeProfiles();
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
							//此处加载默认配置"classpath:/,classpath:/config/,file:./,file:./config/";
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
						addLoadedPropertySources();
						applyActiveProfiles(defaultProperties);
					});
		}

        其中需要重点关注initializeProfiles()方法,该方法实现了不同配置环境的切换,通过配置spring.profiles.active的值能够指定不同的环境。这里其实我是有些疑惑的,我们可以在properties默认文件中添加spring.profiles.active属性也能够生效,但是active文件的属性读取时机似乎在默认文件之前,在默认文件加载的时候会对其中的spring.profiles.active和spring.profiles.include属性过滤,添加相关文件。

	private void initializeProfiles() {
			// The default profile for these purposes is represented as null. We add it
			// first so that it is processed first and has lowest priority.
			this.profiles.add(null);
			//spring.profiles.active属性对应的文件,可用于切换不同环境,获取"spring.profiles.active"对应的值
			Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
			//spring.profiles.include属性对应的文件
			Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
			List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
			this.profiles.addAll(otherActiveProfiles);
			// Any pre-existing active profiles set via property sources (e.g.
			// System properties) take precedence over those added in config files.
			this.profiles.addAll(includedViaProperty);
			addActiveProfiles(activatedViaProperty);
			if (this.profiles.size() == 1) { // only has null profile
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					this.profiles.add(defaultProfile);
				}
			}
		}

            active文件只会添加一次。

void addActiveProfiles(Set<Profile> profiles) {
			if (profiles.isEmpty()) {
				return;
			}
			//active文件只有一个会生效
			if (this.activatedProfiles) {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
				}
				return;
			}
			this.activatedProfiles = true;
		
		}

 

         这里有一个问题,我们的一般配置是spring.profiles.active=dev,那么dev是如何对应application-dev.properties文件的。

		private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
				// Try profile-specific file & profile section in profile file (gh-340)
				//文件名拼接 prefix=classpath:/application,profile=dev,fileExtension=.properties
				//profileSpecificFile=classpath:/application-dev.properties
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			// Also try the profile-specific section (if any) of the normal file
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

四、springboot和apollo属性加载的先后顺序

在大概流程是先加载springboot的系统属性和环境属性,然后执行ApolloApplicationContextInitializer的initialize方法,再加载springboot的默认properties属性,再执行ApolloApplicationContextInitializer的postProcessEnvironment方法,下面给出关键代码。

	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//调用环境中的applicationListeners
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

	private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
        //调用postProcessEnvironment方法,实现默认属性和apollo属性加载
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		}
	}


总结

本文分析了apollo客户端的启动流程,并对apollo属性和springboot原有属性的加载顺序进行分析,如有遗漏之处。还请大家指出。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Apollo Routing 是 Apollo Client 的一个功能模块,用于处理客户端的路由和导航。它提供了一组 API 和工具,使得在 GraphQL 客户端中进行页面导航和路由管理变得更简单。 Apollo Routing 的源码主要包括以下几个部分: 1. `Router` 类:这是 Apollo Routing 的核心类,负责管理路由状态和导航。它使用浏览器的 `history` API 来监听 URL 变化,并根据变化更新路由状态。`Router` 类还提供了一组方法,用于注册和处理路由变化的回调函数。 2. `Route` 组件:这是一个 React 组件,用于声明式地定义路由与组件的映射关系。每个 `Route` 组件都包含一个 `path` 属性和一个 `component` 属性,用于指定 URL 匹配规则和对应的组件。 3. `Link` 组件:这是一个 React 组件,用于生成带有正确 URL 的链接。它会通过 `Router` 类提供的 API 来更新 URL,并触发路由变化。 4. `Switch` 组件:这是一个 React 组件,用于在多个 `Route` 组件之间选择匹配的路由。它会遍历所有子组件,并渲染第一个匹配成功的 `Route` 组件。 5. `useRouter` Hook:这是一个自定义的 React Hook,用于在函数组件中获取 `Router` 实例。它会利用 React 的上下文(context)机制,从根组件向下传递 `Router` 实例。 以上是 Apollo Routing 的主要源码组成部分。通过分析这些源码,我们可以更深入地了解 Apollo Routing 的实现原理和使用方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值