Spring Boot 启动原理全解析(三)加载参数原理

prepareEnvironment是SpringBoot启动过程中负责创建Environment、加载所有配置(本地+Nacos)、严格控制配置优先级、触发监听器读取配置文件的核心阶段。很多人搞不懂:配置从哪来?顺序怎么定?监听器何时触发?配置如何注入到@Configuration@Component

注意:prepareEnvironment 只负责加载配置,不负责将配置绑定到@Configuration/@Component类,绑定发生在容器刷新阶段

本文基于SpringBoot 2.7.18源码 ,逐行拆解prepareEnvironment执行流程,结合配置加载顺序(本地+Nacos)、监听器触发机制、PropertySource优先级保障、配置绑定原理,搭配实战案例(如MyBatis配置绑定),彻底讲透环境准备阶段的底层逻辑。


一、先看整体定位:prepareEnvironment在启动流程中的位置

1. 源码入口(run方法)

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 1. 加载运行监听器(SpringApplicationRunListener)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 2. 封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 3. 【核心】环境准备(本文重点)
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        
        // 4. 创建容器、刷新上下文...
        ConfigurableApplicationContext context = createApplicationContext();
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);
        // ...
    } catch (Throwable ex) {
        // ...
    }
}

定位prepareEnvironment容器创建前 执行,负责构建完整的配置环境,是连接「启动初始化」和「容器创建」的桥梁。

2. prepareEnvironment方法全貌(逐行源码)

java 复制代码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 1. 创建或获取Environment对象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    // 2. 配置基础环境:添加命令行参数、系统属性等
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    
    // 3. 绑定配置源到Environment(内部封装PropertySource)
    ConfigurationPropertySources.attach(environment);
    
    // 4. 【关键】触发环境准备事件(所有监听器在此执行)
    listeners.environmentPrepared(bootstrapContext, environment);
    
    // 5. 调整默认配置优先级(移到最后)
    DefaultPropertiesPropertySource.moveToEnd(environment);
    
    // 6. 校验环境前缀(禁止通过配置设置)
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
            "Environment prefix cannot be set via properties.");
    
    // 7. 将Environment配置绑定到SpringApplication(属性映射)
    bindToSpringApplication(environment);
    
    // 8. 转换Environment类型(非自定义环境时)
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    
    // 9. 再次绑定配置源
    ConfigurationPropertySources.attach(environment);
    
    return environment;
}

核心入参

  • listeners:运行监听器(唯一实现EventPublishingRunListener),用于触发事件;
  • bootstrapContext:引导上下文(SpringCloud用,加载Nacos等远程配置);
  • applicationArguments:封装后的命令行参数(--server.port=8080等)。

二、第一步:创建Environment(getOrCreateEnvironment)

1. 源码逻辑

java 复制代码
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    // 根据应用类型创建对应Environment
    switch (this.webApplicationType) {
        case SERVLET:
            return new ApplicationServletEnvironment(); // Web应用(默认)
        case REACTIVE:
            return new ApplicationReactiveEnvironment(); // 响应式Web
        case NONE:
            return new StandardEnvironment(); // 非Web应用
    }
    throw new IllegalStateException("Unknown web application type: " + this.webApplicationType);
}

2. Environment结构(核心:PropertySource集合)

ConfigurableEnvironment内部维护一个有序的PropertySource列表 ,每个PropertySource代表一个配置源(如命令行、application.yml、Nacos配置等):

java 复制代码
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    // 配置源集合(有序,后添加的优先级更高)
    MutablePropertySources getPropertySources();
    // ...
}

关键特性后添加的PropertySource覆盖先添加的同名属性------这是SpringBoot配置优先级的核心保障。


三、第二步:基础环境配置(configureEnvironment)

1. 源码逻辑

java 复制代码
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    // 1. 添加默认配置(SpringApplication.setDefaultProperties设置的)
    if (this.defaultProperties != null) {
        environment.getPropertySources().addLast(
                new MapPropertySource("springApplicationDefaultProperties", this.defaultProperties));
    }
    // 2. 配置命令行参数(--key=value)
    configurePropertySources(environment, args);
    // 3. 激活默认Profile(无spring.profiles.active时,默认default)
    configureProfiles(environment, args);
}

2. 核心配置源添加(优先级从低到高)

  • 默认配置springApplicationDefaultProperties(代码设置,优先级最低);
  • 命令行参数commandLineArgs--server.port=8080,优先级高);
  • 系统属性systemProperties-Dspring.profiles.active=dev);
  • 环境变量systemEnvironment(操作系统环境变量)。

此时状态 :基础配置源已添加,但未加载application.yml、Nacos等核心配置文件 ------这些配置由后续监听器加载。


四、第三步:触发监听器(listeners.environmentPrepared)------配置加载的核心

这是整个环境准备阶段最关键的一步所有配置文件(本地+Nacos)都在这一步加载 ,由监听器监听ApplicationEnvironmentPreparedEvent事件并执行。

1. 事件触发源码

java 复制代码
// SpringApplicationRunListeners.java
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    doWithListeners("spring.boot.application.environment-prepared",
            (listener) -> listener.environmentPrepared(bootstrapContext, environment));
}

// EventPublishingRunListener.java(唯一运行监听器)
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
        ConfigurableEnvironment environment) {
    // 发布环境准备事件
    this.initialMulticaster.multicastEvent(
            new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

事件类型ApplicationEnvironmentPreparedEvent(环境准备完成事件)。

2. 监听器执行:哪些监听器响应此事件?

spring.factories加载的应用监听器中,有2个核心监听器处理配置加载:

  1. ConfigDataEnvironmentPostProcessor (SpringBoot 2.4+,替代旧版ConfigFileApplicationListener):加载本地配置文件application.ymlapplication-{profile}.yml等);
  2. EnvironmentPostProcessorApplicationListener :代理所有EnvironmentPostProcessor(如Nacos的NacosPropertySourcePostProcessor),加载远程配置(Nacos、Apollo等)。
(1)本地配置加载:ConfigDataEnvironmentPostProcessor

本地配置文件内部优先级(从高到低)

复制代码
1. jar 外部 application-{profile}.properties/yml
2. jar 外部 application.properties/yml
3. jar 内部 application-{profile}.properties/yml
4. jar 内部 application.properties/yml
5. jar 内部 bootstrap-{profile}.properties/yml
6. jar 内部 bootstrap.properties/yml

同文件优先级properties > yml > yaml(同名配置,properties覆盖yml)。
带profile优先级xxx-{profile}.yml > xxx.yml

(2)Nacos远程配置加载:NacosPropertySourcePostProcessor

SpringCloud场景下,Nacos配置加载由NacosPropertySourcePostProcessor响应事件执行:
Nacos内部优先级(从高到低)

复制代码
1. Nacos {application}-{profile}.yml
2. Nacos {application}.yml
3. Nacos shared-configs 共享配置

最终全局配置优先级(从高到低,后覆盖前)

复制代码
1. 命令行参数(--key=value)
2. JVM系统属性(-Dkey=value)
3. 操作系统环境变量
4. Nacos 远程配置(profile → 应用名 → 共享)
5. 本地jar外配置(file:./config → file:./)
6. 本地jar内 application-{profile}
7. 本地jar内 application 默认
8. 本地jar内 bootstrap-{profile}
9. 本地jar内 bootstrap 默认
10. SpringApplication 默认配置(最低)

3. 监听器执行方式:同步还是异步?

同步执行 !源码中SimpleApplicationEventMulticaster默认无线程池 ,监听器在主线程串行执行

java 复制代码
// SimpleApplicationEventMulticaster.java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    // 无Executor时,同步执行
    if (executor != null) {
        executor.execute(() -> this.invokeListener(listener, event));
    } else {
        this.invokeListener(listener, event); // 主线程直接调用
    }
}

结论 :配置加载是同步串行的,加载顺序严格可控,避免并发导致的配置混乱。


五、第四步:PropertySource优先级保障(moveToEnd+bindToSpringApplication)

1. DefaultProperties移到最后(moveToEnd)

java 复制代码
DefaultPropertiesPropertySource.moveToEnd(environment);
  • 作用:将默认配置 (优先级最低)移到PropertySources列表末尾,确保所有其他配置覆盖默认配置

2. 配置绑定到SpringApplication(bindToSpringApplication)

java 复制代码
bindToSpringApplication(environment);
  • 作用:将Environment中的配置绑定到SpringApplication的属性 (如spring.main.web-application-typespring.main.banner-mode),实现「配置驱动启动行为」。
  • 注意 :这一步只绑定SpringApplication自身不绑定任何业务Bean

六、第五步:Environment类型转换与最终绑定

1. 转换Environment类型(convertEnvironmentIfNecessary)

java 复制代码
if (!this.isCustomEnvironment) {
    EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
    environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
  • 作用:统一Environment实现类(如Web应用统一为ApplicationServletEnvironment),确保后续容器使用时类型一致。

2. 最终绑定配置源(ConfigurationPropertySources.attach)

java 复制代码
ConfigurationPropertySources.attach(environment);
  • 作用:将所有PropertySource封装为配置属性源 ,供后续@Value@ConfigurationProperties绑定使用。

七、配置如何保存、获取并绑定到配置类?

1. 配置保存位置

所有配置最终存储在 ConfigurableEnvironmentMutablePropertySources 集合 中,每个配置源是一个PropertySource实现类(如MapPropertySourceResourcePropertySourceNacosPropertySource)。

2. 配置获取方式

(1)代码中获取(Environment接口)
java 复制代码
@Autowired
private Environment environment;

// 获取单个配置
String port = environment.getProperty("server.port");
// 获取带默认值的配置
String dbUrl = environment.getProperty("spring.datasource.url", "jdbc:mysql://localhost:3306/test");
(2)配置类绑定(@ConfigurationProperties)

MyBatis配置 为例,将application.yml中的MyBatis配置绑定到配置类:

yaml 复制代码
# application.yml
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.entity
  configuration:
    map-underscore-to-camel-case: true
java 复制代码
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "mybatis") // 绑定前缀为mybatis的配置
public class MyBatisProperties {
    private String mapperLocations;
    private String typeAliasesPackage;
    private Configuration configuration;

    // getter/setter
    public static class Configuration {
        private boolean mapUnderscoreToCamelCase;
        // getter/setter
    }
}

八、@ConfigurationProperties 原理

1. 说明

prepareEnvironment 只加载配置,不绑定Bean!
Bean的配置绑定 = 容器刷新时执行!

2. 绑定时机(执行到哪一步开始绑)

复制代码
run()
  → prepareEnvironment()  【加载配置】
  → createApplicationContext() 【创建容器】
  → prepareContext()
  → refreshContext()
     → refresh()
        → finishBeanFactoryInitialization()【创建bean】
           → 创建 @ConfigurationProperties Bean
               → 执行绑定  ←------------------ 这里才开始绑!

3. 绑定源码在哪里

核心绑定处理器
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor

真正绑定方法

java 复制代码
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    bind(bean, beanName); // ← 配置绑定入口
    return bean;
}

真正绑定逻辑

java 复制代码
private void bind(Object bean, String beanName) {
    ConfigurationProperties annotation = getAnnotation(bean.getClass());
    if (annotation != null) {
        Bindable<?> target = Bindable.ofInstance(bean);
        this.binder.bind(annotation.prefix(), target);
    }
}

4. 怎么绑上去的

  1. 扫描@ConfigurationProperties(prefix="xxx")
  2. 通过BinderEnvironment.getPropertySources()取值
  3. 通过反射调用set方法注入字段
  4. 完成配置绑定

九、完整流程总结

1. 执行链路

复制代码
run() → prepareEnvironment()
├─ getOrCreateEnvironment() → 创建ConfigurableEnvironment
├─ configureEnvironment() → 添加命令行、系统属性、环境变量
├─ ConfigurationPropertySources.attach()
├─ listeners.environmentPrepared() → 触发事件
│  └─ 加载本地配置(application > bootstrap)
│  └─ 加载Nacos远程配置
├─ DefaultPropertiesPropertySource.moveToEnd()
├─ bindToSpringApplication()
├─ convertEnvironmentIfNecessary()
└─ ConfigurationPropertySources.attach()

容器刷新后 → 才执行 @ConfigurationProperties 绑定

2. 全局配置优先级

复制代码
1. 命令行参数
2. JVM系统属性(-D)
3. 操作系统环境变量
4. Nacos远程配置
5. 外部application-{profile}
6. 外部application默认
7. 内部application-{profile}
8. 内部application默认
9. 内部bootstrap-{profile}
10. 内部bootstrap默认
11. 默认配置

同名文件.properties > .yml > .yaml
带profilexxx-{profile} > xxx默认


十、面试高频问题

  1. prepareEnvironment阶段做了什么?

    答:创建Environment、加载所有配置、触发监听器、控制配置优先级、绑定SpringApplication自身属性。不绑定业务Bean

  2. 配置优先级是什么?

    答:命令行 > 系统属性 > 环境变量 > Nacos > 本地application > 本地bootstrap。

  3. 监听器是同步还是异步?

    答:主线程同步串行执行

  4. @ConfigurationProperties在哪绑的?

    答:容器刷新finishBeanFactoryInitialization阶段,由ConfigurationPropertiesBindingPostProcessor执行绑定。


十一、总结

prepareEnvironment是SpringBoot启动的配置中枢

  • 只负责加载配置,不负责绑定Bean
  • 所有配置存在Environment.getPropertySources()
  • 配置优先级严格有序、后加载覆盖先加载
  • 本地/远程配置统一由监听器加载
  • Bean配置绑定在容器刷新阶段执行
相关推荐
小松加哲4 小时前
Spring Boot 启动原理全解析(一)
spring boot启动原理
cehuishi952719 天前
高程转换的几种方式
坐标转换·参数设置·1985高程转换
almighty278 个月前
C#WPF控制USB摄像头参数:曝光、白平衡等高级设置完全指南
开发语言·c#·wpf·usb相机·参数设置