Apollo 整合 Spring Boot 原理分析

一、使用

1、apollo是携程开发的一个开源的分布式配置中心,spring boot项目如果需要整合apollo,需要在pom.xml中添加如下依赖:

<dependency>
   <groupId>com.ctrip.framework.apollo</groupId>
   <artifactId>apollo-client</artifactId>
   <version>1.6.2</version>
</dependency>

2、需要在spring boot项目的application.properties(或yml)文件中添加如下配置:

// 该应用在apollo里对应的app id
app.id=xxx
// apollo服务端地址
apollo.meta=http://127.0.0.1:8080
// 启用apollo
apollo.bootstrap.enabled=true
// 开启饥饿加载
apollo.bootstrap.eagerLoad.enabled=true

3、启动类上加上@EnableApolloConfig注解

二、原理分析

1、找到apollo-clinet.jar下的spring.factories文件并打开,如下所示:
Apollo 整合 Spring Boot 原理分析
配置了ApolloAutoConfiguration和ApolloApplicationContextInitializer两个类。第一个类是自动配置类,第二个类实现了ApplicationContextInitializer和EnvironmentPostProcessor接口,是apollo能整合spring boot的一个关键因素。

2、spring boot在启动过程中(参考:spring boot 启动流程),首先会加载ApplicationContextInitializer和ApplicationListener(事件监听器,代表:ConfigFileApplicationListener),然后加载SpringApplicationRunListener(发布事件用,代表:EventPublishingRunListener),再后面会调用org.springframework.boot.SpringApplication#prepareEnvironment 方法,这时候会发布一个ApplicationEnvironmentPreparedEvent事件,正好ConfigFileApplicationListener监听了这个事件,代码如下:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		// 从spring.factories中加载EnvironmentPostProcessor的实现类,从而找到ApolloApplicationContextInitializer
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			// 这里会调用到ApolloApplicationContextInitializer重写的postProcessEnvironment方法
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

	List<EnvironmentPostProcessor> loadPostProcessors() {
		return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
	}

	// ConfigFileApplicationListener本身也实现了EnvironmentPostProcessor,所以他自己也重写了postProcessEnvironment方法,这个方法就是将配置文件属性源添加到环境中。
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}

3、看看com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer#postProcessEnvironment 方法做了什么事

  @Override
  public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {

    // should always initialize system properties like app.id in the first place
    // 找app.id和apollo.meta这些属性
    initializeSystemProperty(configurableEnvironment);

	// 判断是否开启了饥饿加载,如果没开启就暂时放弃加载apollo中的配置
    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,如果启用了就开始加载
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);

    if (bootstrapEnabled) {
      initialize(configurableEnvironment);
    }

  }

    protected void initialize(ConfigurableEnvironment environment) {
		// 先判断是不是已经加载过了,刚开始肯定没有加载,如果加载过了就不重复加载了
	    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
	      //already initialized
	      return;
	    }
	
		// apollo配置的默认namespace是application.properties,也可以通过apollo.bootstrap.namespaces自行指定
	    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) {
	      // 加载配置
	      Config config = ConfigService.getConfig(namespace);
	
	      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
	    }
	
		// 这里将apollo的配置放到PropertySources的第一个,所以他的配置优先级是最高的
		// spring boot读取配置就是按照优先级从高到低的顺序加载,如果读到了就直接返回了,所以要注意配置覆盖问题
	    environment.getPropertySources().addFirst(composite);
    }

4、上面的流程是开启了apollo饥饿加载时的流程,如果没有开启,在prepareEnvironment 环节不会加载apollo的配置,而是在调用org.springframework.boot.SpringApplication#prepareContext 方法时加载,这个方法会调用 applyInitializers(context),也就是调用ApplicationContextInitializer#initialize(context), ApolloApplicationContextInitializer实现了ApplicationContextInitializer接口,并重写了initialize方法

  @Override
  public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();

	// 同样先判断是否开启了apollo
    if (!environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false)) {
      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);

	// 这里就是调用第3步中提到过的initialize方法,前面没有加载过的话这里也会进行加载
    initialize(environment);
  }

如果是在这个时候加载,有些配置就不能配置在apollo中,比如日志相关的配置(如logging.level.root=info或logback-spring.xml中的参数),因为日志相关的加载在这一步之前。顺序是:加载 Bootstrap 属性和应用程序属性 -----> 加载 Apollo 配置属性 ----> 初始化日志系统

上一篇:php直播源码,Android 获取空间大小


下一篇:开源量化框架Catalyst中文教程(2) -- 安装 官网教材