一、使用
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文件并打开,如下所示:
配置了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 配置属性 ----> 初始化日志系统