当前位置: 首页 > news >正文

SpringBoot源码解析(五):准备应用环境

SpringBoot源码系列文章

SpringBoot源码解析(一):SpringApplication构造方法

SpringBoot源码解析(二):引导上下文DefaultBootstrapContext

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码解析(四):解析应用参数args

SpringBoot源码解析(五):准备应用环境


目录

  • 前言
  • 一、入口
  • 二、环境实例ApplicationServletEnvironment
    • 1、PropertyResolver
    • 2、Environment
    • 3、ConfigurablePropertyResolver
    • 4、ConfigurableEnvironment
    • 5、ConfigurableWebEnvironment
    • 6、AbstractEnvironment
    • 7、StandardEnvironment
    • 8、StandardServletEnvironment
    • 9、ApplicationServletEnvironment
  • 三、配置环境
    • 1、命令行参数属性源
    • 2、配置属性源
  • 四、触发的应用监听器
    • 1、EnvironmentPostProcessorApplicationListener
      • 1.1、RandomValuePropertySourceEnvironmentPostProcessor
      • 1.2、SystemEnvironmentPropertySourceEnvironmentPostProcessor
      • 1.3、SpringApplicationJsonEnvironmentPostProcessor
      • 1.4、ConfigDataEnvironmentPostProcessor
    • 2、AnsiOutputApplicationListener
    • 3、BackgroundPreinitializer
    • 4、FileEncodingApplicationListener
  • 五、默认属性源
  • 六、绑定spring.main配置到SpringApplication对象
  • 总结

前言

  在前文中,我们深入解析了启动类main函数中args参数被解析为选项参数和非选项参数的过程。接下来,我们将探讨SpringBoot启动时应用环境的准备过程,包括读取配置文件设置环境变量的步骤。

SpringBoot版本2.7.18SpringApplication的run方法的执行逻辑如下,本文将详细介绍第4小节:准备应用环境

// SpringApplication类方法
public ConfigurableApplicationContext run(String... args) {// 记录应用启动的开始时间long startTime = System.nanoTime();// 1.创建引导上下文,用于管理应用启动时的依赖和资源DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;// 配置无头模式属性,以支持在无图形环境下运行// 将系统属性 java.awt.headless 设置为 trueconfigureHeadlessProperty();// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑SpringApplicationRunListeners listeners = getRunListeners(args);// 启动开始方法(发布开始事件、通知应用监听器ApplicationListener)listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 3.解析应用参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 4.准备应用环境,包括读取配置文件和设置环境变量ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置是否忽略 BeanInfo,以加快启动速度configureIgnoreBeanInfo(environment);// 5.打印启动BannerBanner printedBanner = printBanner(environment);// 6.创建应用程序上下文context = createApplicationContext();// 设置应用启动的上下文,用于监控和管理启动过程context.setApplicationStartup(this.applicationStartup);// 7.准备应用上下文,包括加载配置、添加 Bean 等prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 8.刷新上下文,完成 Bean 的加载和依赖注入refreshContext(context);// 9.刷新后的一些操作,如事件发布等afterRefresh(context, applicationArguments);// 计算启动应用程序的时间,并记录日志Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 10.通知监听器应用启动完成listeners.started(context, timeTakenToStartup);// 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑callRunners(context, applicationArguments);}catch (Throwable ex) {// 12.处理启动过程中发生的异常,并通知监听器handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 13.计算应用启动完成至准备就绪的时间,并通知监听器Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {// 处理准备就绪过程中发生的异常handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}// 返回已启动并准备就绪的应用上下文return context;
}

一、入口

// 4.准备应用环境,包括读取配置文件和设置环境变量
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  • 初始化并配置Spring应用环境
// SpringApplication类方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 4.1.获取环境实例 ApplicationServletEnvironmentConfigurableEnvironment environment = getOrCreateEnvironment();// 4.2.配置环境// 将命令行参数传递给环境配置configureEnvironment(environment, applicationArguments.getSourceArgs());// 添加配置属性源到环境中,使其能够解析相关配置属性ConfigurationPropertySources.attach(environment);// 4.3.通知监听器,环境已准备好listeners.environmentPrepared(bootstrapContext, environment);// 4.4.将默认属性源移到环境属性源列表的末尾DefaultPropertiesPropertySource.moveToEnd(environment);// spring.main.environment-prefix是SpringBoot用于管理上下文环境的一部分// 不应该由用户直接修改,如果被用户定义,则抛错Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");// 4.5.绑定spring.main环境配置到SpringApplication对象上bindToSpringApplication(environment);// 如果环境不是自定义的,则进行环境转换,确定必要的环境类型if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}// 再次设置配置属性源,确保配置属性源在第一位ConfigurationPropertySources.attach(environment);// 返回配置完成的环境对象return environment;
}

二、环境实例ApplicationServletEnvironment

  getOrCreateEnvironment()方法的核心是创建一个 ApplicationServletEnvironment实例,下面将重点研究该类。

先看类图:从上到下逐一分析

在这里插入图片描述

1、PropertyResolver

  PropertyResolver是Spring核心框架中的一个接口,提供了解析属性值的统一方法。它支持从多种配置源(如系统属性环境变量配置文件等)获取属性值,广泛用于环境配置、占位符解析等场景。

  • 属性检查
    • 判断某个属性是否存在
    • 方法:containsProperty(String key)
  • 获取属性值
    • 获取指定键的属性值,支持默认值、类型转换等
    • 方法:getProperty(String key)、getProperty(String key, String defaultValue)、getProperty(String key, Class targetType) 等
  • 必需属性值
    • 获取指定键的属性值,若找不到则抛出异常
    • 方法:getRequiredProperty(String key)、getRequiredProperty(String key, Class targetType)
  • 占位符解析
    • 解析字符串中的 ${…} 占位符,替换为对应的属性值
    • 方法:resolvePlaceholders(String text)、resolveRequiredPlaceholders(String text)
// 用于解析属性值的接口,支持从底层源解析属性
public interface PropertyResolver {// 判断指定的属性键是否可用,即该键对应的值是否不为nullboolean containsProperty(String key);// 返回与指定键关联的属性值,如果未找到则返回null@NullableString getProperty(String key);// 返回与指定键关联的属性值,如果未找到则返回默认值String getProperty(String key, String defaultValue);// 返回与指定键关联的属性值,并将其转换为指定的目标类型,如果未找到则返回null@Nullable<T> T getProperty(String key, Class<T> targetType);// 返回与指定键关联的属性值,并将其转换为目标类型,如果未找到则返回默认值<T> T getProperty(String key, Class<T> targetType, T defaultValue);// 返回与指定键关联的属性值(不能为null)String getRequiredProperty(String key) throws IllegalStateException;// 返回与指定键关联的属性值,并将其转换为目标类型(不能为null)<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;// 解析给定文本中的 ${...} 占位符,并用对应的属性值替换。// 未解析的占位符会被忽略并原样返回。String resolvePlaceholders(String text);// 解析给定文本中的 ${...} 占位符,并用对应的属性值替换// 未解析的占位符将抛出 IllegalArgumentException 异常String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

2、Environment

  Environment是Spring框架中的一个核心接口,用于表示应用程序的运行环境。它扩展了PropertyResolver接口,既负责属性解析,也负责Profile管理。在Spring中,它的主要用途是管理配置文件(Profiles)和属性(Properties)

// 表示当前应用程序运行环境的接口
public interface Environment extends PropertyResolver {// 返回当前环境中激活的ProfilesString[] getActiveProfiles();// 返回当前环境中默认激活的 ProfilesString[] getDefaultProfiles();// 检查给定的 Profile 表达式是否与当前激活的 Profiles 匹配default boolean matchesProfiles(String... profileExpressions) {return acceptsProfiles(Profiles.of(profileExpressions));}// 检查一个或多个给定的 Profiles 是否被当前环境接受@Deprecatedboolean acceptsProfiles(String... profiles);// 检查给定的 Profiles 谓词是否与当前激活的或默认的 Profiles 匹配boolean acceptsProfiles(Profiles profiles);
}

3、ConfigurablePropertyResolver

  ConfigurablePropertyResolver是Spring中PropertyResolver的扩展接口,为属性解析器添加了额外的配置能力,主要用于高级属性管理和占位符解析。它允许自定义属性解析行为,如类型转换服务占位符格式以及验证必需的属性

// 用于在将属性值从一种类型转换为另一种类型时使用
public interface ConfigurablePropertyResolver extends PropertyResolver {// 获取用于属性值类型转换的 ConfigurableConversionService 实例ConfigurableConversionService getConversionService();// 设置类型转换服务void setConversionService(ConfigurableConversionService conversionService);// 设置占位符的前缀,默认为 ${// 示例:若设置为 #{,则占位符形如 #{property}void setPlaceholderPrefix(String placeholderPrefix);// 设置占位符的后缀,默认为 }// 示例:与 setPlaceholderPrefix 配合使用,解析 #{property}void setPlaceholderSuffix(String placeholderSuffix);// 设置占位符和默认值之间的分隔符,默认值为 :// 示例:${property:defaultValue} 表示如果 property 未定义,则返回 defaultValuevoid setValueSeparator(@Nullable String valueSeparator);// 设置是否忽略无法解析的嵌套占位符// true:保留未解析的占位符(如 ${unresolved})// false:遇到未解析的占位符时抛出异常void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);// 设置必须存在的属性void setRequiredProperties(String... requiredProperties);// 验证所有必需属性是否存在并解析为非 nullvoid validateRequiredProperties() throws MissingRequiredPropertiesException;
}

4、ConfigurableEnvironment

  ConfigurableEnvironment是Spring框架中的一个核心接口,扩展了Environment和ConfigurablePropertyResolver接口,提供了对Profile属性源(PropertySources) 的动态管理功能。它主要用于应用启动前的环境配置,允许开发者根据需求自定义属性解析规则和Profile配置。

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {// 设置当前环境的 Active Profiles(激活的配置)void setActiveProfiles(String... profiles);// 向当前的 Active Profiles 集合中添加一个 Profilevoid addActiveProfile(String profile);// 设置默认激活的 Profile 集合void setDefaultProfiles(String... profiles);// 返回当前环境的属性源集合,允许动态操作属性源MutablePropertySources getPropertySources();// 返回JVM的系统属性(System.getProperties())Map<String, Object> getSystemProperties();// 返回操作系统的环境变量(System.getenv())Map<String, Object> getSystemEnvironment();// 将父环境的 Active Profiles、Default Profiles 和 属性源 合并到当前环境中void merge(ConfigurableEnvironment parent);
}

5、ConfigurableWebEnvironment

  ConfigurableWebEnvironment是Spring框架中ConfigurableEnvironment的特化接口,主要用于基于Servlet环境的Web应用程序。它扩展了 ConfigurableEnvironment,并增加了与Servlet相关的配置功能,确保在ServletContextServletConfig可用时,能够尽早初始化与 Servlet 环境相关的属性源。

public interface ConfigurableWebEnvironment extends ConfigurableEnvironment {// 使用给定的 ServletContext 和(可选的)ServletConfig 初始化属性源。// 该方法会将占位符性质的属性源替换为实际的 Servlet 上下文或配置属性源。// 调用时机:Spring Boot 中的内嵌容器(如 Tomcat、Jetty)启动时void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig);
}

6、AbstractEnvironment

  AbstractEnvironment作为Environment实现的基础类,主要提供对配置文件(Profiles)属性源(PropertySources)的管理。它支持定义和管理激活配置文件(Active Profiles) 以及 默认配置文件(Default Profiles)。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {// 激活配置文件集合private final Set<String> activeProfiles = new LinkedHashSet<>();// 默认配置文件集合 getReservedDefaultProfiles() = 字符串“default”private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());// 属性源集合,上篇文章有介绍,属性源就是一组属性map键值对,这里集合就是多组键值对private final MutablePropertySources propertySources;// 属性解析器private final ConfigurablePropertyResolver propertyResolver;public AbstractEnvironment() {this(new MutablePropertySources());}protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;this.propertyResolver = createPropertyResolver(propertySources);customizePropertySources(propertySources);}// 自定义属性源,对属性源增删改查做操作protected void customizePropertySources(MutablePropertySources propertySources) {// 默认无操作,子类可覆盖}// 省略其他方法
}

  PropertySource表示属性来源的抽象概念,每个属性源(PropertySource)封装了一组键值对(key-value),并可以根据键解析属性值。用于从各种来源(如系统属性环境变量配置文件等)加载属性

public abstract class PropertySource<T> {// 属性源名称,唯一标识protected final String name;// 一般是map键值对protected final T source;...
}

  MutablePropertySources用于管理多个属性源(PropertySource)的集合。它提供了一个可变的数据结构,允许开发者动态地添加、删除、替换或调整属性源的顺序。

  • addFirst(PropertySource<?> propertySource):将属性源添加到集合的首位,优先级最高
  • addLast(PropertySource<?> propertySource):将属性源添加到集合的末尾,优先级最低
  • addBefore(String relativePropertySourceName, PropertySource<?> propertySource):在指定名称的属性源之前插入
  • addAfter(String relativePropertySourceName, PropertySource<?> propertySource):在指定名称的属性源之后插入
public class MutablePropertySources implements PropertySources {// 属性源集合private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();...
}

7、StandardEnvironment

  StandardEnvironment提供了对系统属性(System Properties)环境变量(Environment Variables)的支持,并在默认情况下将这两个属性源添加到 MutablePropertySources中。

  AbstractEnvironment构造方法中会调用customizePropertySources方法,也就是创建ApplicationServletEnvironment实例就是添加两个属性源,名称为systemProperties为的JVM系统属性(如 java.version、java.home 等)和名称为systemEnvironment环境变量(PATH、HOME等操作系统相关变量)。

public class StandardEnvironment extends AbstractEnvironment {// 系统环境变量属性源的名称public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";// JVM 系统属性源的名称public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";public StandardEnvironment() {}protected StandardEnvironment(MutablePropertySources propertySources) {super(propertySources);}// 自定义操作属性源,父类构造中会调用@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// addLast将属性放到最后,优先级最低,JVM优先级高于环境变量propertySources.addLast(// 通过 System.getProperties() 提供的 JVM 系统属性new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(// 通过 System.getenv() 提供的环境变量new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}
}

8、StandardServletEnvironment

  StandardServletEnvironment是Spring框架中的一个类,继承自StandardEnvironment,用于为基于Servlet的Web应用程序提供专门的Environment实现。它扩展了标准环境(StandardEnvironment)的功能,增加了对Servlet相关属性(如ServletConfigServletContext)的支持。

  属性优先级:ServletConfig > ServletContext > JNDI > 系统属性 > 环境变量。

  在应用上下文启动时,StandardServletEnvironment会将占位符属性(StubPropertySource)替换为实际的ServletConfig和ServletContext属性源。

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {// ServletContext 初始化参数属性源的名称public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";// ServletConfig 初始化参数属性源的名称public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";// JNDI 属性源名称public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";// 防御性地引用 JNDI API,用于 JDK 9+(可选的 java.naming 模块)private static final boolean jndiPresent = ClassUtils.isPresent("javax.naming.InitialContext", StandardServletEnvironment.class.getClassLoader());public StandardServletEnvironment() {}protected StandardServletEnvironment(MutablePropertySources propertySources) {super(propertySources);}// 自定义属性源集合,添加超类贡献的属性源@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// 添加 ServletConfig 初始化参数占位符属性源propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));// 添加 ServletContext 初始化参数占位符属性源propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));// 如果 JNDI 可用且默认环境可用,则添加 JNDI 属性源if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));}// 调用父类的自定义属性源方法super.customizePropertySources(propertySources);}// 初始化属性源,将 Servlet 相关的属性源(如 ServletContext 和 ServletConfig)初始化为真实属性源@Overridepublic void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);}
}

9、ApplicationServletEnvironment

  AbstractEnvironment构造方法中会调用createPropertyResolver方法,也就是创建ApplicationServletEnvironment实例就会创建此自定义属性解析器,重写修改自定义属性解析器为ConfigurationPropertySourcesPropertyResolver

class ApplicationServletEnvironment extends StandardServletEnvironment {// 表示不从任何特定的属性(如 spring.profiles.active)获取活动配置文件@Overrideprotected String doGetActiveProfilesProperty() {return null;}// 表示不从任何特定的属性(如 spring.profiles.default)获取默认配置文件@Overrideprotected String doGetDefaultProfilesProperty() {return null;}// 自定义属性解析器,// 默认使用ConfigurationPropertySources的实现,这里重写实现为ConfigurationPropertySourcesPropertyResolver@Overrideprotected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {return ConfigurationPropertySources.createPropertyResolver(propertySources);}
}

三、配置环境

1、命令行参数属性源

// 4.2.配置环境
// 将命令行参数传递给环境配置
configureEnvironment(environment, applicationArguments.getSourceArgs());// 配置环境方法
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {// 如果需要添加转换服务,用于在应用程序中管理和执行各种类型之间的转换if (this.addConversionService) {// 设置转换服务为一个新的 ApplicationConversionService 实例environment.setConversionService(new ApplicationConversionService());}// 配置属性源(将命令行参数添加到属性源集合中,放第一位优先级最高)configurePropertySources(environment, args);// 配置活动的配置文件,空实现configureProfiles(environment, args);
}
  • 命令行参数属性源添加到环境的属性源集合中,且放第一位
/*** 配置属性源的方法。* @param environment 可配置的环境对象,用于管理属性源* @param args        应用程序启动时传入的命令行参数*/
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {// 获取当前环境中的属性源集合MutablePropertySources sources = environment.getPropertySources();// 如果存在默认属性(defaultProperties),将其添加或合并到属性源中if (!CollectionUtils.isEmpty(this.defaultProperties)) {DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);}// 如果启用了命令行属性解析,并且命令行参数非空if (this.addCommandLineProperties && args.length > 0) {// 获取命令行属性源的默认名称 “commandLineArgs”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));}}
}

2、配置属性源

  ConfigurationPropertySourcesPropertySource这个类的作用是将 Spring 配置属性源(如 .properties 文件、.yml 文件、环境变量等)转换为一个统一的PropertySource,并将这些属性源集成到Spring的Environment中。具体解析内容后面单独讲

  将名称为configurationProperties,对象为SpringConfigurationPropertySources的属性源添加到环境的属性源集合中,且放第一位,优先级最高

// 4.2.配置环境
// 添加配置属性源到环境中,使其能够解析相关配置属性
ConfigurationPropertySources.attach(environment);public static void attach(Environment environment) {// 确保传入的 environment 是 ConfigurableEnvironment 类型Assert.isInstanceOf(ConfigurableEnvironment.class, environment);// 获取当前环境的属性源集合MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();// 检查是否已经附加了名为“configurationProperties”的属性源PropertySource<?> attached = getAttached(sources);// 如果尚未附加,或者附加的属性源未正确使用当前属性源集合if (attached == null || !isUsingSources(attached, sources)) {// 创建一个新的 ConfigurationPropertySourcesPropertySource,// 它包装了当前的属性源集合attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources));}// 移除已有的同名属性源(如果存在)sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);// 将新的属性源添加到属性源集合的最前面,确保其具有最高优先级sources.addFirst(attached);
}

四、触发的应用监听器

// 4.3.通知监听器,环境已准备好
listeners.environmentPrepared(bootstrapContext, environment);

  在之前文章SpringBoot源码解析(三):启动开始阶段已经介绍过,广播器将特定的事件(之前的应用启动事件,现在这里就是准备环境事件)推给合适的监听器(匹配监听器的事件类型,这里就是匹配准备环境事件的监听器)。下面我们自己所有匹配到监听器的具体内容。

在这里插入图片描述

1、EnvironmentPostProcessorApplicationListener

  EnvironmentPostProcessorApplicationListener的作用是监听ApplicationEnvironmentPreparedEvent事件,加载并执行所有EnvironmentPostProcessor实现类,用于在SpringBoot应用启动过程中对环境配置 (Environment) 进行动态调整和扩展,例如加载额外的配置源、设置属性或修改激活的 profiles,确保在应用上下文初始化之前完成环境的定制化操作。

在这里插入图片描述

  • 环境后置处理器EnvironmentPostProcessor的实现类也是从META-INF/spring.factories文件中加载
// 当应用环境准备事件触发时执行的方法
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {// 获取应用的环境配置ConfigurableEnvironment environment = event.getEnvironment();// 获取 Spring 应用对象SpringApplication application = event.getSpringApplication();// 遍历所有的 EnvironmentPostProcessor(环境后置处理器)for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),event.getBootstrapContext())) {// 调用每个后置处理器的 postProcessEnvironment 方法,处理环境配置postProcessor.postProcessEnvironment(environment, application);}
}// 环境后置处理器接口
@FunctionalInterface
public interface EnvironmentPostProcessor {void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
  • 下面看下加载到的所有环境后置处理器

在这里插入图片描述

1.1、RandomValuePropertySourceEnvironmentPostProcessor

  RandomValuePropertySourceEnvironmentPostProcessor是SpringBoot提供的一个内置类,用于在Spring应用程序启动时向环境中添加一个RandomValuePropertySource

  它通过 RandomValuePropertySource 提供生成随机值的功能(如随机字符串、整数或 UUID),供配置文件中使用。

  • 生成随机值
    • 支持 random.intrandom.longrandom.uuidrandom.value
    • 允许在配置文件中动态生成随机值,常用于密钥、端口号等需要唯一性或随机性的场景
  • 在配置文件中用法
    # 动态生成一个 UUID
    my.secret.key=${random.uuid}
    # 一个介于 1024 和 65535 之间的随机整数
    my.random.port=${random.int[1024,65535]}
    

RandomValuePropertySource原理很简单,就是讲属性源对象设置为new Random()

在这里插入图片描述

1.2、SystemEnvironmentPropertySourceEnvironmentPostProcessor

  在上面环境实例ApplicationServletEnvironment实例化阶段,就添加了环境变量属性源SystemEnvironmentPropertySource,这里把它替换成OriginAwareSystemEnvironmentPropertySource,主要目的是增强对配置属性来源的追踪能力,从而提升可维护性和调试性。

举例:

logging.level.org.springframework.core.env=DEBUG
2024-11-24 12:34:56.123 DEBUG o.s.b.c.c.ConfigFileApplicationListener - Loaded property source 'applicationConfig: [classpath:/application.properties]'
2024-11-24 12:34:56.124 DEBUG o.s.b.c.c.ConfigFileApplicationListener - Loaded property source 'applicationConfig: [classpath:/application.yml]'
2024-11-24 12:34:56.125 DEBUG o.s.b.e.e.PropertySourcesPropertyResolver - Found key 'my.app.port' in 'systemEnvironment' with value '8080'

1.3、SpringApplicationJsonEnvironmentPostProcessor

  作用是将环境变量SPRING_APPLICATION_JSONJSON格式内容解析为配置属性。

举例:

export SPRING_APPLICATION_JSON='{"server.port":8081,"spring.datasource.url":"jdbc:mysql://localhost:3306/mydb"}'
java -jar my-app.jar
  • 优先级通常高于文件配置(如 application.properties 或 application.yml)
  • 在容器化部署(如 Kubernetes、Docker)中,环境变量是常见的配置传递方式

关键代码片段:

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {String json = environment.getProperty("SPRING_APPLICATION_JSON");if (StringUtils.hasText(json)) {try {Map<String, Object> map = parseJson(json);PropertySource<?> propertySource = new MapPropertySource("spring.application.json", map);environment.getPropertySources().addFirst(propertySource);} catch (Exception ex) {throw new IllegalStateException("Invalid SPRING_APPLICATION_JSON: " + json, ex);}}
}

1.4、ConfigDataEnvironmentPostProcessor

  ConfigDataEnvironmentPostProcessor是SpringBoot2.4引入的一部分,作为Spring配置加载机制的新实现的核心组件。它取代了早期的ConfigFileApplicationListener,专注于从多种来源(如 application.propertiesapplication.yml、环境变量等)加载配置数据。具体解析内容后面单独讲

2、AnsiOutputApplicationListener

  AnsiOutputApplicationListener是SpringBoot提供的一个监听器,用于在应用启动时配置ANSI控制台输出(彩色日志或彩色信息) 的行为。它可以根据环境配置决定是否启用ANSI颜色支持,并设置相关属性。

3、BackgroundPreinitializer

  BackgroundPreinitializer是SpringBoot内置的一个类,用于在后台线程中异步加载某些耗时的初始化操作,从而减少应用主线程的阻塞时间,提高应用启动性能。

  主线程可以专注于初始化Spring上下文,而耗时的操作(如 JUL日志桥接、默认的Validator实例化等)在后台进行,从而加快应用的总体启动速度。

在这里插入图片描述

4、FileEncodingApplicationListener

  FileEncodingApplicationListener是SpringBoot的一个监听器,用于检测和验证 JVM 的文件编码(file.encoding)属性,确保其值符合应用程序的要求。如果文件编码未设置为期望的值,可能会引发警告或异常。

五、默认属性源

  DefaultProperties(默认属性源)移动到Spring环境(Environment)中属性源的最后面。这通常用于确保用户配置的属性(如文件配置、环境变量、命令行参数等)优先于默认属性,从而允许用户覆盖默认配置。

// 4.4.将默认属性源移到环境属性源列表的末尾
DefaultPropertiesPropertySource.moveToEnd(environment);

在这里插入图片描述

  • SpringBoot默认情况下没有添加默认属性源,用户可以自定义设置默认值
@Configuration
public class DefaultPropertyConfig {@Beanpublic PropertySource<?> defaultPropertySource() {return new MapPropertySource("defaultProperties", Map.of("server.port", "8080"));}
}

六、绑定spring.main配置到SpringApplication对象

// 4.5.绑定spring.main环境配置到SpringApplication对象上
bindToSpringApplication(environment);

  它的作用是从ConfigurableEnvironment中提取所有与spring.main前缀相关的配置,并将这些配置值赋值给SpringApplication类中的相应字段。

在这里插入图片描述

绑定的属性

属性名默认值描述
spring.main.banner-modeconsole启动横幅的显示模式,默认输出到控制台
spring.main.lazy-initializationfalse是否启用懒加载模式
spring.main.log-startup-infotrue是否输出启动日志信息
spring.main.allow-bean-definition-overriding环境决定(truefalse是否允许覆盖 Bean 定义
spring.main.web-application-type自动检测应用类型:noneservletreactive
spring.main.register-shutdown-hooktrue是否注册 JVM 的关闭钩子(用于资源清理)

总结

  本文深入探讨了SpringBoot启动过程中应用环境的准备阶段,包括从配置文件命令行参数系统属性等多种配置源加载属性,以及对环境对象进行调整和绑定。通过SpringBoot的灵活机制,开发者可以轻松扩展和调整应用环境,从而满足各种复杂的场景需求。

相关文章:

SpringBoot源码解析(五):准备应用环境

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args Sp…...

MySQL面试-1

InnoDB中ACID的实现 先说一下原子性是怎么实现的。 事务要么失败&#xff0c;要么成功&#xff0c;不能做一半。聪明的InnoDB&#xff0c;在干活儿之前&#xff0c;先将要做的事情记录到一个叫undo log的日志文件中&#xff0c;如果失败了或者主动rollback&#xff0c;就可以通…...

nginx配置不缓存资源

方法1 location / {index index.html index.htm;add_header Cache-Control no-cache,no-store;try_files $uri $uri/ /index.html;#include mime.types;if ($request_filename ~* .*\.(htm|html)$) {add_header Cache-Control "private, no-store, no-cache, must-revali…...

PHP导出EXCEL含合计行,设置单元格格式

PHP导出EXCEL含合计行&#xff0c;设置单元格格式&#xff0c;水平居中 垂直居中 public function exportSalary(Request $request){//水平居中 垂直居中$styleArray [alignment > [horizontal > Alignment::HORIZONTAL_CENTER,vertical > Alignment::VERTICAL_CE…...

RabbitMQ 之 死信队列

一、死信的概念 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理 解&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或者直接到 queue 里了&#xff0c;consumer 从 queue 取出消息进行…...

【创建型设计模式】单例模式

【创建型设计模式】单例模式 这篇博客接下来几篇都将阐述设计模式相关内容。 接下来的顺序大概是&#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。 一、什么是单例模式 单例模式是一种创建型设计模式&#xff0c;它保证一个类仅有一个实例&#…...

Charles抓包工具-笔记

摘要 概念&#xff1a; Charles是一款基于 HTTP 协议的代理服务器&#xff0c;通过成为电脑或者浏览器的代理&#xff0c;然后截取请求和请求结果来达到分析抓包的目的。 功能&#xff1a; Charles 是一个功能全面的抓包工具&#xff0c;适用于各种网络调试和优化场景。 它…...

Go语言使用 kafka-go 消费 Kafka 消息教程

Go语言使用 kafka-go 消费 Kafka 消息教程 在这篇教程中&#xff0c;我们将介绍如何使用 kafka-go 库来消费 Kafka 消息&#xff0c;并重点讲解 FetchMessage 和 ReadMessage 的区别&#xff0c;以及它们各自适用的场景。通过这篇教程&#xff0c;你将了解如何有效地使用 kafk…...

【论文笔记】Number it: Temporal Grounding Videos like Flipping Manga

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Number it: Temporal Grou…...

C语言菜鸟入门·关键字·int的用法

目录 1. int关键字 1.1 取值范围 1.2 符号类型 1.3 运算 1.3.1 加法运算() 1.3.2 减法运算(-) 1.3.3 乘法运算(*) 1.3.4 除法运算(/) 1.3.5 取余运算(%) 1.3.6 自增()与自减(--) 1.3.7 位运算 2. 更多关键字 1. int关键字 int 是一个关键字&#xff0…...

基于企业微信客户端设计一个文件下载与预览系统

在企业内部沟通与协作中&#xff0c;文件分享和管理是不可或缺的一部分。企业微信&#xff08;WeCom&#xff09;作为一款广泛应用于企业的沟通工具&#xff0c;提供了丰富的API接口和功能&#xff0c;帮助企业进行高效的团队协作。然而&#xff0c;随着文件交换和协作的日益增…...

昇思MindSpore第七课---文本解码原理

1. 文本解码原理 文本解码是将模型的输出&#xff08;通常是概率分布或词汇索引&#xff09;转换为可读的自然语言文本的过程。在生成文本时&#xff0c;常见的解码方法包括贪心解码、束搜索&#xff08;BeamSearch&#xff09;、随机采样等。 2 实践 2.1 配置环境 安装mindn…...

C# 数据结构之【图】C#图

1. 图的概念 图是一种重要的数据结构&#xff0c;用于表示节点&#xff08;顶点&#xff09;之间的关系。图由一组顶点和连接这些顶点的边组成。图可以是有向的&#xff08;边有方向&#xff09;或无向的&#xff08;边没有方向&#xff09;&#xff0c;可以是加权的&#xff…...

传输控制协议(TCP)和用户数据报协议(UDP)

一、传输控制协议&#xff08;TCP&#xff09; 传输控制协议&#xff08;Transmission Control Protocol&#xff0c;TCP&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;由 IETF 的 RFC 793 定义。 它通过三次握手建立连接&#xff0c;确保数…...

【Python爬虫】Scrapy框架实战---百度首页热榜新闻

如何利用Scrapy框架实战提取百度首页热榜新闻的排名、标题和链接 一、安装Scrapy库 二、创建项目&#xff08;以BaiduSpider为例&#xff09; scrapy startproject BaiduSpider生成每个文件的功能&#xff1a; 二、 创建爬虫脚本&#xff08;爬虫名&#xff1a;news&#xff…...

采用python3.12 +django5.1 结合 RabbitMQ 和发送邮件功能,实现一个简单的告警系统 前后端分离 vue-element

一、开发环境搭建和配置 #mac环境 brew install python3.12 python3.12 --version python3.12 -m pip install --upgrade pip python3.12 -m pip install Django5.1 python3.12 -m django --version #用于检索系统信息和进程管理 python3.12 -m pip install psutil #集成 pika…...

Qt 实现网络数据报文大小端数据的收发

1.大小端数据简介 大小端&#xff08;Endianness&#xff09;是计算机体系结构的一个术语&#xff0c;它描述了多字节数据在内存中的存储顺序。以下是大小端的定义和它们的特点&#xff1a; 大端&#xff08;Big-Endian&#xff09; 在大端模式中&#xff0c;一个字的最高有效…...

[译]Elasticsearch Sequence ID实现思路及用途

原文地址:https://www.elastic.co/blog/elasticsearch-sequence-ids-6-0 如果 几年前&#xff0c;在Elastic&#xff0c;我们问自己一个"如果"问题&#xff0c;我们知道这将带来有趣的见解&#xff1a; "如果我们在Elasticsearch中对索引操作进行全面排序会怎样…...

Java基于SpringBoot+Vue的藏区特产销售平台

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

12-表的约束

知识背景 表的约束&#xff0c;就是在表中的数据上加上约束&#xff0c;也被称为数据完整性约束。数据完整性约束的目的是为了不被规定的、不符合规范的数据进入数据库 在录入数据库或数据发生变化时&#xff0c;DBMS(数据库管理系统)会按照一定的约束条件对数据进行监测&…...

【人工智能】深度学习入门:用TensorFlow实现多层感知器(MLP)模型

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 多层感知器(MLP)是一种基础的神经网络结构,广泛应用于分类和回归任务。作为深度学习的重要组成部分,理解并实现MLP是学习更复杂神经网络模型的基础。本文将介绍多层感知器的核心概念、数学原理,并使用…...

【Go】-go中的锁机制

目录 一、锁的基础知识 1. 互斥量/互斥锁 2. CAS&#xff08;compare and swap&#xff09; 3. 自旋锁 4. 读写锁 5. 乐观锁 & 悲观锁 6. 死锁 二、go中锁机制 1. Mutex-互斥锁 2. RWMutex-读写锁 2.1 RWMutex流程概览 2.2 写锁饥饿问题 2.3. golang的读写锁源…...

c ++零基础可视化——vector

c 零基础可视化——vector 初始化 vector<int> v0(5); // 0 0 0 0 0 vector<int> v1(5, 1); // 1 1 1 1 1 vector<int> v2{1, 2, 3} // 1 2 3 vector<int> v3(v1); // 1 1 1 1 1 vector<vector<int>> v4(2, vect…...

Centos 7 安装 Docker 最新版本

文章目录 一、卸载旧版本二、安装最新版本docker三、问题解决3.1 启动docker报错3.2 启动容器报错 一、卸载旧版本 #如果之前安装过旧版本的Docker&#xff0c;可以使用下面命令卸载 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest …...

构建高效在线教育:SpringBoot课程管理系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理在线课程管理系统的相关信息成为必然。开发…...

二进制与网络安全的关系

二进制与网络安全的关系 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以…...

【计算机网络】网段划分

一、为什么有网段划分 IP地址 网络号(目标网络) 主机号(目标主机) 网络号: 保证相互连接的两个网段具有不同的标识 主机号: 同一网段内&#xff0c;主机之间具有相同的网络号&#xff0c;但是必须有不同的主机号 互联网中的每一台主机&#xff0c;都要隶属于某一个子网 -&…...

VB、VBS、VBA的区别及作用

VB、VBS 和 VBA 是三种与微软 Visual Basic 相关的编程语言或环境&#xff0c;它们在功能和用途上有所不同&#xff1a; # Visual Basic (VB) Visual Basic 是一种面向对象的编程语言&#xff0c;最初由微软公司开发。它是一种高级编程语言&#xff0c;旨在简化开发过程&…...

深度学习中的循环神经网络(RNN)与时间序列预测

一、循环神经网络&#xff08;RNN&#xff09;简介 循环神经网络&#xff08;Recurrent Neural Networks&#xff0c;简称RNN&#xff09;是一种专门用于处理序列数据的神经网络架构。与传统神经网络不同&#xff0c;RNN具有内部记忆能力&#xff0c;能够捕捉数据中的时间依赖…...

Unity 设计模式-原型模式(Prototype Pattern)详解

原型模式 (Prototype Pattern) 原型模式 (Prototype Pattern) 是一种创建型设计模式&#xff0c;它允许通过复制现有的对象来创建新对象&#xff0c;而不是通过直接实例化类。这意味着你可以通过克隆原型对象来生成新的实例&#xff0c;而不必依赖类的构造函数。该模式的核心思…...