public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//1.推测web应用类型(NONE REACTIVE SERVLET)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2.从spring.factories中获取BootstrapRegistryInitializer对象
this.bootstrapRegistryInitializers =
this.getBootstrapRegistryInitializersFromSpringFactories();
//3.从spring.factories中获取ApplicationContextInitializer对象
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//4.从spring.factories中获取ApplicationListener对象
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//5.推测出Main类 (main()方法所在的类)
this.mainApplicationClass = this.deduceMainApplicationClass();
}
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
/**从spring.factories中获取SpringApplicationRunListeners 对象
* 默认会拿到一个EventPublishingRunListener ,他会启动过程的各个阶段发布对应的事件
**/
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//将run()的参数封装为DefaultApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置文件的入口
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
//根据应用类型创建Spring容器
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新Spring容器, 会解析配置类 扫描 启动Webserver
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
//调用applicationArguments 和CommandLineRunner
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
Spring Boot 加载配置文件的机制是其核心功能之一,它通过一系列源码实现从不同来源加载配置,并支持灵活的配置管理。以下是 Spring Boot 加载配置文件的源码解析及其实现原理的详细说明。(上述两段是加载配置文件的源码片段)
1. Spring Boot 加载配置文件的整体流程
Spring Boot 加载配置文件的流程可以分为以下几个步骤:
-
初始化
Environment
:在应用启动时,创建并初始化Environment
对象。 -
加载默认配置文件:从
application.properties
或application.yml
加载配置。 -
加载 Profile 特定的配置文件:根据激活的 Profile 加载
application-{profile}.properties
或application-{profile}.yml
。 -
加载外部化配置:从命令行参数、环境变量、JNDI 等外部来源加载配置。
-
合并配置:将所有配置来源合并到
Environment
中,供应用程序使用。
2. 源码解析
以下是 Spring Boot 加载配置文件的核心源码解析。
2.1 SpringApplication.run()
Spring Boot 应用的启动入口是 SpringApplication.run()
方法。在这个方法中,会初始化 Environment
并加载配置文件。
源码位置:org.springframework.boot.SpringApplication
public ConfigurableApplicationContext run(String... args) {
// ...
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// ...
}
说明:
-
prepareEnvironment()
方法负责创建和配置Environment
对象。
2.2 prepareEnvironment()
prepareEnvironment()
方法会调用 configureEnvironment()
来加载配置文件。
源码位置:org.springframework.boot.SpringApplication
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// 创建 Environment 对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置 Environment,加载配置文件
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 触发环境准备事件
listeners.environmentPrepared(environment);
// 将 Environment 绑定到 SpringApplication
bindToSpringApplication(environment);
return environment;
}
说明:
-
getOrCreateEnvironment()
:根据应用类型(Web 或非 Web)创建StandardEnvironment
或StandardServletEnvironment
。 -
configureEnvironment()
:加载配置文件和其他外部化配置。
2.3 configureEnvironment()
configureEnvironment()
方法会调用 configurePropertySources()
和 configureProfiles()
来加载配置文件和激活 Profile。
源码位置:org.springframework.boot.SpringApplication
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
说明:
-
configurePropertySources()
:加载命令行参数、默认配置文件等。 -
configureProfiles()
:设置激活的 Profile。
2.4 ConfigFileApplicationListener
ConfigFileApplicationListener
是 Spring Boot 加载配置文件的核心类。它监听 ApplicationEnvironmentPreparedEvent
事件,并加载 application.properties
或 application.yml
文件。
源码位置:org.springframework.boot.context.config.ConfigFileApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
}
说明:
-
onApplicationEnvironmentPreparedEvent()
:在环境准备完成后触发,加载配置文件。
2.5 Loader.load()
Loader
是 ConfigFileApplicationListener
的内部类,负责实际加载配置文件。
源码位置:org.springframework.boot.context.config.ConfigFileApplicationListener.Loader
void load() {
for (String location : getSearchLocations()) {
for (String name : getSearchNames()) {
load(location, name);
}
}
}
说明:
-
getSearchLocations()
:获取配置文件的搜索路径(如classpath:/
、file:./config/
等)。 -
getSearchNames()
:获取配置文件的名称(如application
)。 -
load(location, name)
:从指定路径加载配置文件。
2.6 PropertySourcesLoader
PropertySourcesLoader
负责将配置文件内容加载到 PropertySource
中。
源码位置:org.springframework.boot.env.PropertySourcesLoader
public PropertySource<?> load(Resource resource) throws IOException {
if (resource.getFilename().endsWith(".yml")) {
return loadYaml(resource);
} else {
return loadProperties(resource);
}
}
说明:
-
loadYaml()
:加载 YAML 格式的配置文件。 -
loadProperties()
:加载 Properties 格式的配置文件。
2.7 Profile
的加载
Spring Boot 支持通过 Profile 加载不同的配置文件。Profiles
和 ProfilePropertySource
负责处理 Profile 相关的配置。
源码位置:org.springframework.core.env.Profiles
public static Profiles of(String... profiles) {
return new Profiles() {
@Override
public boolean matches(Predicate<String> predicate) {
// ...
}
};
}
说明:
-
Profiles
:管理 Profile 的激活状态。 -
ProfilePropertySource
:根据激活的 Profile 加载特定的配置文件。
3. Spring Boot 加载配置文件的实现原理
Spring Boot 加载配置文件的实现原理可以总结为以下几点:
-
多来源加载:
-
支持从类路径、外部目录、环境变量、命令行参数等多种来源加载配置。
-
-
优先级机制:
-
后加载的配置会覆盖先加载的配置,命令行参数的优先级最高。
-
-
Profile 支持:
-
通过
spring.profiles.active
指定激活的 Profile,加载对应的配置文件。
-
-
外部化配置:
-
支持从外部文件、环境变量、JNDI 等加载配置,适用于云原生和容器化部署。
-
4. 示例:Spring Boot 加载配置文件的流程
以下是一个完整的示例,展示 Spring Boot 如何加载配置文件。
4.1 默认配置文件
application.properties
:
server.port=8080
spring.profiles.active=dev
4.2 Profile 特定的配置文件
application-dev.properties
:
server.port=8081
4.3 Java 代码
@RestController
public class MyController {
@Value("${server.port}")
private String port;
@GetMapping("/port")
public String getPort() {
return "Server port: " + port;
}
}
4.4 运行结果
-
如果激活的 Profile 是
dev
,则server.port
的值为8081
。 -
如果没有激活 Profile,则
server.port
的值为8080
。
5. 总结
Spring Boot 加载配置文件的机制非常灵活,支持多来源、多格式的配置加载。通过 Environment
、PropertySource
、ConfigFileApplicationListener
等核心类和组件,Spring Boot 实现了配置文件的加载、合并和优先级管理。理解这些源码和机制,可以帮助我们更好地使用和扩展 Spring Boot 的配置功能。