Loading...
墨滴

lyq

2021/12/19  阅读:66  主题:橙心

Spring Boot外部化配置源码分析

spring boot版本为:2.1.6.RELEASE


前言

Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)。

理解springboot外部化配置原理,有助于帮助我们理解spring cloud config、nacos config等分布式配置中心。

源码分析

Environment的初始化

在springboot启动流程的run方法中,有一个 prepareEnvironment 方法,这个方法就是用来准备Environment这个对象的。

SpringApplication

public ConfigurableApplicationContext run(String... args) {
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
 SpringApplicationRunListeners listeners = getRunListeners(args);
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  // 准备Environment
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
  Banner printedBanner = printBanner(environment);
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] 
{ ConfigurableApplicationContext.class }, context);
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  refreshContext(context);
  afterRefresh(context, applicationArguments);
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
  listeners.started(context);
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

进入prepareEnvironment(listeners, applicationArguments)方法:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
   ApplicationArguments applicationArguments)
 
{
 // Create and configure the environment
 // 创建environment对象
 ConfigurableEnvironment environment = getOrCreateEnvironment();
 // 配置environment对象
 configureEnvironment(environment, applicationArguments.getSourceArgs());
 // 发送ApplicationEnvironmentPreparedEvent类型的事件
 listeners.environmentPrepared(environment);
 bindToSpringApplication(environment);
 if (!this.isCustomEnvironment) {
  environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    deduceEnvironmentClass());
 }
 ConfigurationPropertySources.attach(environment);
 return environment;
}

这个方法主要就是创建和配置spring容器的环境信息,进入getOrCreateEnvironment看它是怎么创建环境的,进去后发现这个方法,就是根据当前的webApplication类型匹配对应的environment,在servlet容器中就是StandardServletEnvironment ,如果是spring webflux,则是StandardReactiveWebEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
 if (this.environment != null) {
  return this.environment;
 }
 switch (this.webApplicationType) {
 case SERVLET:
  return new StandardServletEnvironment();
 case REACTIVE:
  return new StandardReactiveWebEnvironment();
 default:
  return new StandardEnvironment();
 }
}

StandardServletEnvironment 是整个spring boot应用运行环境的实现类,后面所有的关于环境相关的配置操作都是基于这个类,它的类的结构图如下:

StandardServletEnvironment的初始化过程会做一些事情,就是配置一些基本的属性来源。StandardServletEnvironment 会初始化父类 AbstractEnvironment ,在这个类的构造方法中,会调用一个自定义配置文件的方法,这个是spring中比较常见的实现手法,前面在看ribbon、eureka中都有看到。

AbstractEnvironment

public AbstractEnvironment() {
 customizePropertySources(this.propertySources);
}

customizePropertySources 这个方法被 StandardServletEnvironment 重写了,所以会调用StandardServletEnvironment 中的 customizePropertySources 方法。不难看出,这里是将几个不同的配置源封装成 StubPropertySource 添加到 MutablePropertySources 中,调用 addLast 是表示一直往最后的位置添加。

  • SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet config的配置信息
  • SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们在web.xml中配置的 context-param 。
  • JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。

StandardServletEnvironment

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
 propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
 propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
 if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
  propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
 }
 // 继续调用父类的方法
 super.customizePropertySources(propertySources);
}

继续调用父类,也就是 StandardEnvironment 类中的 customizePropertySources 方法。

  • SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: 系统变量,通过System.setProperty设置的变量,默认可以看到 java.version 、 os.name 等。SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: 系统环境变量,也就是我们配置JAVA_HOME的地方。

StandardEnvironment

@Override
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()));
}

这里要明确一点,就是添加PropertySource的目的其实就是要告诉Environment,解析哪些位置的属性文件进行加载。而在这个添加过程中,所有的添加都是基于 addLast ,也就是最早添加的PropertySource会放在最前面。 systemEnvironment 是在 systemProperties 前面,这点很重要。因为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高。

MutablePropertySources

在上面的代码中可以看到,所有的外部资源配置都是添加到了一个MutablePropertySources对象中,这个对象封装了属性资源的集合。而从 MutablePropertySources 命名来说,Mutable是一个可变的意思,也就是意味着它动态的管理了PropertySource的集合。

public class MutablePropertySources implements PropertySources {

 private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

 // 省略...
}

通过上面的类图可以发现AbstractEnvironment 实现了文件解析器ConfigurablePropertyResolver ,而在下面这段代码中我们把 MutablePropertySources 传递到PropertySourcesPropertyResolver 中。这样就可以让 AbstractEnvironment 具备文件解析的功能,只是这个功能,委托给了PropertySourcesPropertyResolver来实现。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
 //省略...
 private final MutablePropertySources propertySources = new MutablePropertySources();

 private final ConfigurablePropertyResolver propertyResolver =
   new PropertySourcesPropertyResolver(this.propertySources);
 //省略...
}

现在回退到SpringApplication类的prepareEnvironment方法,我们继续来看 configureEnvironment 这个方法,这个方法有三个作用:

  • addConversionService :添加类型转化的服务,我们知道properties文件中配置的属性都是String类型的,而转化为Java对象之后要根据合适的类型进行转化,而 ConversionService 是一套通用的转化方案,请参考《SpringMVC之类型转换Converter》,这里把这个转化服务设置到当前的Environment,很显然,就是为Environment配置解析时提供一个类型转化的解决方案。
  • configurePropertySources: 配置Environment中的propertysources
  • configureProfiles :配置profiles
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
 if (this.addConversionService) {
  ConversionService conversionService = ApplicationConversionService.getSharedInstance();
  environment.setConversionService((ConfigurableConversionService) conversionService);
 }
 configurePropertySources(environment, args);
 configureProfiles(environment, args);
}

进入configurePropertySources方法:

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
 MutablePropertySources sources = environment.getPropertySources();
 if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
  sources.addLast(new MapPropertySource("defaultProperties"this.defaultProperties));
 }
 if (this.addCommandLineProperties && args.length > 0) {
  String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
  if (sources.contains(name)) {
   PropertySource<?> source = sources.get(name);
   CompositePropertySource composite = new CompositePropertySource(name);
   composite.addPropertySource(
     new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
   composite.addPropertySource(source);
   sources.replace(name, composite);
  }
  else {
   sources.addFirst(new SimpleCommandLinePropertySource(args));
  }
 }
}
  • 设置 defaultProperties 属性来源,此处可以在构造SpringApplication对象时,通过他的setDefaultProperties方法传入额外的配置。
  • 设置commandLineProperties来源,如果设置了命令行参数,则会加载SimpleCommandLinePropertySource 作为propertySource

到目前为止,还是在初始化外部化配置的数据来源。接着进入configureProfiles方法,这个方法就比较容易理解,就是配置当前激活的profiles,将当前的activeProfiles设置到enviroment中。这样就能够使得我们完成不同环境下配置的获取问题。

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
 environment.getActiveProfiles(); // ensure they are initialized
 // But these ones should go first (last wins in a property key clash)
 Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
 profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
 environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

此时environment中的activeProfiles集合还是空的。additionalProfiles可以通过SpringApplication对象的setAdditionalProfiles方法设置。

经过上面的操作spring的配置信息都已加载完成,但有一个很重要的配置还没有加载,那就是springboot的yml或properties文件中的配置信息,现在回退到SpringApplication类的prepareEnvironment类。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
   ApplicationArguments applicationArguments)
 
{
 // 省略...
 listeners.environmentPrepared(environment);
 // 省略...
 return environment;
}

进入listeners.environmentPrepared方法:

SpringApplicationRunListeners

public void environmentPrepared(ConfigurableEnvironment environment) {
 for (SpringApplicationRunListener listener : this.listeners) {
  listener.environmentPrepared(environment);
 }
}

循环遍历SpringApplicationRunListener对象集合,然后调用它的environmentPrepared方法。在springboot环境中,默认在META-INF/spring.factories中,配置的实现类是EventPublishingRunListener

EventPublishingRunListener

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
 this.initialMulticaster
   .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

内部会委托给SimpleApplicationEventMulticaster对象,进行广播ApplicationEnvironmentPreparedEvent事件。在这个类内部最终会循环遍历spring容器中实现了ApplicationListener的bean,然后调用它的onApplicationEvent方法。

SimpleApplicationEventMulticaster

@Override
public void multicastEvent(ApplicationEvent event) {
 multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 Executor executor = getTaskExecutor();
 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  if (executor != null) {
   executor.execute(() -> invokeListener(listener, event));
  }
  else {
   invokeListener(listener, event);
  }
 }
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
 ErrorHandler errorHandler = getErrorHandler();
 if (errorHandler != null) {
  try {
   doInvokeListener(listener, event);
  }
  catch (Throwable err) {
   errorHandler.handleError(err);
  }
 }
 else {
  doInvokeListener(listener, event);
 }
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
 try {
  listener.onApplicationEvent(event);
 }
 catch (ClassCastException ex) {
  String msg = ex.getMessage();
  if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
   // Possibly a lambda-defined listener which we could not resolve the generic event type for
   // -> let's suppress the exception and just log a debug message.
   Log logger = LogFactory.getLog(getClass());
   if (logger.isTraceEnabled()) {
    logger.trace("Non-matching event type for listener: " + listener, ex);
   }
  }
  else {
   throw ex;
  }
 }
}

以上代码其实也是spring事件机制背后的实现原理。

在springboot环境中,ConfigFileApplicationListener类实现了ApplicationListener接口,那么进入ConfigFileApplicationListener类会看到一个onApplicationEvent方法:

ConfigFileApplicationListener

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

因为现在发布的是ApplicationEnvironmentPreparedEvent类型的事件,所以会进入onApplicationEnvironmentPreparedEvent方法中:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
 postProcessors.add(this);
 AnnotationAwareOrderComparator.sort(postProcessors);
 for (EnvironmentPostProcessor postProcessor : postProcessors) {
  postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
 }
}

此方法主要做了三件事:

  • spring.factories文件中,加载实现了EnvironmentPostProcessor接口的类。
  • 将当前对象加入到postProcessors列表中,因为当前类实现了EnvironmentPostProcessor接口
  • 循环遍历postProcessors列表,调用postProcessEnvironment方法

由于当前类实现了EnvironmentPostProcessor接口,所以会调用它的postProcessEnvironment方法:

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
 addPropertySources(environment, application.getResourceLoader());
}

进入addPropertySources方法:

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
 RandomValuePropertySource.addToEnvironment(environment);
 new Loader(environment, resourceLoader).load();
}

这个方法做两个事情:

  • 添加一个RandomValuePropertySourceEnvironmentMutablePropertySources中,放在系统环境变量的后面
  • 加载spring boot中的配置信息,比如application.yml或者application.properties

进入Loaderload方法:

ConfigFileApplicationListener#Loader

public void load() {
 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 (profile != null && !profile.isDefaultProfile()) {
   addProfileToEnvironment(profile.getName());
  }
  load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
  this.processedProfiles.add(profile);
 }
 resetEnvironmentProfiles(this.processedProfiles);
 load(nullthis::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
 addLoadedPropertySources();
}

这块的代码就不继续深入分析了,有点绕,感兴趣的小伙伴深入去看看。load 所做的事情如下:

  • 获取默认的配置文件路径,有4种: classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/
  • 遍历所有的路径,拼装配置文件名称。
  • 再遍历解析器,选择yml或者properties解析,将解析结果添加到集合MutablePropertySources当中。

至此,springBoot中的资源文件加载完毕,解析顺序从上到下,所以前面的配置文件会覆盖后面的配置文件。可以看到 application.properties 的优先级最低,系统变量和环境变量的优先级相对较高。

扩展外部化配置属性源

扩展点

  1. 基于 EnvironmentPostProcessor 扩展
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
  1. 基于 ApplicationEnvironmentPreparedEvent事件扩展
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener
  1. 基于 SpringApplicationRunListener 扩展
public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
  1. 基于 ApplicationContextInitializer 扩展
public class CustomApplicationContextInitializer implements ApplicationContextInitializer
  1. 基于 ApplicationPreparedEvent 扩展
public class ApplicationPreparedEventListener implements ApplicationListener

配置

classpath下添加配置文件 META-INF/spring.factories, 内容如下:

# Spring Application Run Listeners

org.springframework.boot.SpringApplicationRunListener=\
springboot.propertysource.extend.listener.CustomSpringApplicationRunListener

 

# Application Context Initializers

org.springframework.context.ApplicationContextInitializer=\
springboot.propertysource.extend.initializer.CustomApplicationContextInitializer

 

# Application Listeners

org.springframework.context.ApplicationListener=\
springboot.propertysource.extend.event.listener.ApplicationEnvironmentPreparedEventListener,\
springboot.propertysource.extend.event.listener.ApplicationPreparedEventListener

 

# Environment Post Processors

org.springframework.boot.env.EnvironmentPostProcessor=\
springboot.propertysource.extend.processor.CustomEnvironmentPostProcessor

以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样,越早越好。

总结

掌握了springboot外部化配置的实现原理,为以后学习和理解spring cloud环境下,加载远程配置中心的配置原理,打下良好的基础。

欢迎关注我的公众号:程序员L札记

更多原创文章,请扫码关注我的微信公众号
更多原创文章,请扫码关注我的微信公众号

lyq

2021/12/19  阅读:66  主题:橙心

作者介绍

lyq