Apollo使用小记

167 阅读4分钟

Q1:如果apollo.bootstrap.namespaces不指定application而是指定了其他的命名空间,例如vtm-admin,会默认加载application中的配置吗?

不会的

在 Apollo 配置中,apollo.bootstrap.namespaces 属性用于指定应用启动时需要加载的命名空间。

如果你只指定了 vtm-admin,那么只有 vtm-admin 这个命名空间的配置会被加载,application 命名空间的配置不会被加载。

如果你希望同时加载 vtm-admin 和 application 的配置,你需要在 apollo.bootstrap.namespaces 属性中同时指定它们,如下所示:

apollo:
  bootstrap:
    namespaces: vtm-admin, application

这样,Apollo 在启动时会先加载 vtm-admin 命名空间的配置,然后加载 application 命名空间的配置。

源码展示: ApolloApplicationContextInitializer#initialize(ConfigurableEnvironment environment)

String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);

ConfigConsts.NAMESPACE_APPLICATION即application,也就是说不指定命名空间的情况下,apollo会默认加载application,一旦指定了命名空间,Apollo 就只会加载指定的那个命名空间。

Q2:使用即使namespaces没有指定application,使用@Value还是可以读取到application中配置(dataBatchSize),这是什么原因?

这是因为在Spring框架中,@Value 注解可以读取到所有的配置源(Property Source)中的配置。

在Apollo中,无论你是否在 apollo.bootstrap.namespaces 中指定 application,application 命名空间的配置都会被加载为一个配置源。

当你在代码中使用 @Value 注解读取 dataBatchSize 配置时,Spring会从所有的配置源中查找这个配置。

如果 vtm-admin 和 application 命名空间中都有 dataBatchSize 这个配置,那么会以namespaces加载顺序靠前的为准。

PS:关于多个namespace的加载顺序以及相同配置项以哪个为准,在Q4章节会详细描述。

所以,即使你已经明确指定了 vtm-admin,在使用 @Value 注解时,仍然可以读取到 application 中的 dataBatchSize 这个配置。

同样的原理,也适用于以下注解:

@ApolloConfig
private Config config;
@ApolloConfigValue("${some.key}")
private String someKey;
@ApolloJsonValue("${some.json.key}")
private SomeObject someObject;

Q3:关于Spring启动时,Apollo的加载时机是什么,如果需要在YML中使用Apollo配置,该如何实现?

Spring加载Apollo配置通常在Spring的ApplicationContext初始化阶段完成。 具体来说,当Spring Boot应用启动时,它会创建一个ApplicationContext,并在这个上下文中注册各种Bean。在这个过程中,如果你的应用使用了Apollo,那么Apollo的配置会被加载并添加到Spring的Environment中。

但是当你需要在yml中使用Apollo配置时,需要进行以下配置

apollo.bootstrap.eagerLoad = true

当apollo.bootstrap.eagerLoad.enabled设置为true时,Apollo配置将在Spring ApplicationContext创建和初始化之前就被加载。这意味着,你可以在Spring的配置文件(如application.yml或application.properties)中使用Apollo的配置项。

spring:
  datasource:
    url: ${db.url}
    username: ${db.username}
    password: ${db.password}

在这个例子中,db.url、db.username和db.password是在Apollo配置中定义的。如果你设置了apollo.bootstrap.eagerLoad.enabled=true,那么Spring在解析application.yml时,就可以获取到这些配置项的值。

反之,如果apollo.bootstrap.eagerLoad.enabled设置为false或者没有设置,那么Apollo配置将在Spring ApplicationContext创建和初始化之后才被加载。这意味着,你不能在Spring的配置文件中使用Apollo的配置项,因为在解析配置文件时,Apollo的配置还没有被加载。

Q4:关于多个namespace的加载顺序,以及同名配置覆盖问题

多个namespace按apollo.bootstrap.namespaces的顺序依次加载。同名配置不会被覆盖,即同名配置以先加载的namespace配置的为准

在Q1中提到ApolloApplicationContextInitializer#initialize(ConfigurableEnvironment environment)加载命名空间,完整代码如下:

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) {
	  Config config = ConfigService.getConfig(namespace);

	  composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
	}

	environment.getPropertySources().addFirst(composite);
}

可以看到for (String namespace : namespaceList) 在依次加载所有namespace中的配置,再调用composite.addPropertySource()函数

private final Set<PropertySource<?>> propertySources = new LinkedHashSet<>();

public void addPropertySource(PropertySource<?> propertySource) {
	this.propertySources.add(propertySource);
}

继续跟踪源码(CompositePropertySource.java)可以看到 propertySources 被定义成 LinkedHashSet,其内部是按照加入的顺序存储的。因此,多个namespace按apollo.bootstrap.namespaces的顺序依次加载。

@Override
@Nullable
public Object getProperty(String name) {
	for (PropertySource<?> propertySource : this.propertySources) {
		Object candidate = propertySource.getProperty(name);
		if (candidate != null) {
			return candidate;
		}
	}
	return null;
}

继续查看获取配置的源码(CompositePropertySource#getProperty(String name)),可以看到if (candidate != null) 的判断,从这里可以看出,如果配置已经存在,就以该配置以准,即同名配置不会被覆盖,即同名配置以先加载的namespace配置的为准。

另外,Q2章节提到,使用某些注解可以读取到所有的配置源(Property Source)中的配置,此时如果只指定了vtm-admin命名空间,而没有指定application,并且vtm-admin和application又有同名的配置,那么就会以指定的vtm-admin为准。