SpringBoot - 启动与自动配置
SpringBoot 启动流程与自动配置:run() 主线 + selectImports
SpringBoot 解决的问题:Spring 本身只是个容器,Web 服务器、Environment、各种框架整合都要你手动配。
SpringBoot 用一个 run() 方法把”建环境→建容器→refresh”串成固定流水线,再用”约定文件列候选 + 条件注解做过滤”的自动配置机制,把配置工作从”你写”变成”你排除”。
// 基于本地 Spring Boot 仓 (2.7.x),org.springframework.boot.SpringApplication
public class SpringApplication {
// 构造器就干了大事:run 之前,应用类型和扩展点已经全部就位
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用类型:看 classpath 上有没有 DispatcherHandler/DispatcherServlet 等标志类
// 结论只有三种:SERVLET / REACTIVE / NONE,后面建什么容器全由它决定
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 从 spring.factories 加载 Initializer 和 Listener —— SPI 扩展点在这里注入
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 靠"造个异常翻栈帧找 main 方法"来定位主类,简单粗暴但有效
this.mainApplicationClass = deduceMainApplicationClass();
}
// 启动主线。整个 SpringBoot 启动就是这一个方法的顺序执行
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 又一次读 spring.factories,拿 SpringApplicationRunListener(默认只有 EventPublishingRunListener)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass); // 广播"开始启动"
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 1. 准备 Environment:此时容器还不存在,但配置文件已经加载完
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 2. 按 webApplicationType 创建对应容器(SERVLET -> AnnotationConfigServletWebServerApplicationContext)
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 3. refresh 前的最后准备:塞 Environment、跑 Initializer、注册主类的 BeanDefinition
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 4. 进入 Spring 经典的 refresh() 十二步,Tomcat 启动、自动配置解析都发生在里面
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments); // ApplicationRunner/CommandLineRunner 在容器就绪后执行
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners); // 失败也走事件广播,FailureAnalyzer 靠这个打友好报错
throw new IllegalStateException(ex);
}
// ... ready 事件广播,省略
return context;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs()); // 命令行参数进 PropertySource,优先级最高
ConfigurationPropertySources.attach(environment);
// 关键:广播 environmentPrepared 事件,EnvironmentPostProcessor 借此加载 application.yml
// 所以"配置文件是监听器加载的"——这就是事件驱动启动的典型用法
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment); // 默认属性挪到最后,保证优先级垫底
// ... 校验与 Environment 类型转换,省略
ConfigurationPropertySources.attach(environment);
return environment;
}
// 策略方法:容器类型不是写死的,由 webApplicationType 决定
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); // 把已经备好的 Environment 塞进容器
postProcessApplicationContext(context);
applyInitializers(context); // 构造器里从 spring.factories 收的 Initializer 此刻执行
listeners.contextPrepared(context);
bootstrapContext.close(context);
// ... 日志与 registerSingleton(springApplicationArguments 等),省略
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 只把主类注册成 BeanDefinition —— 其余所有 Bean 都等 refresh 时从这颗种子长出来
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context); // JVM 关闭钩子,优雅停机的入口
}
refresh(context); // 最终调 AbstractApplicationContext#refresh,回到 Spring 主干
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances); // @Order 排序,扩展点执行顺序可控
return instances;
}
}// 基于本地 Spring 仓 (5.3.x),org.springframework.core.io.support.SpringFactoriesLoader
// Spring 版 SPI:对比 JDK ServiceLoader,它一个文件能声明多种类型(key=接口 value=实现列表),且带缓存
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// 全量加载后按类型名取 —— 只返回类名字符串,不实例化,留给调用方决定
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result; // 按 ClassLoader 缓存:一次 IO,整个启动期反复复用
}
result = new HashMap<>();
try {
// getResources 会扫所有 jar 里的同名文件 —— 每个 starter 的 spring.factories 都会被读到
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
}// 基于本地 Spring Boot 仓 (2.7.x),org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
// @EnableAutoConfiguration 通过 @Import 引入它。注意它是 DeferredImportSelector:
// 延迟到用户自己的配置类全部解析完才执行,否则 @ConditionalOnMissingBean 判断不准
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) { // spring.boot.enableautoconfiguration=false 可全局关掉
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 自动配置五步:拿候选 -> 去重 -> 算排除 -> 剔除 -> 条件过滤
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 100+ 候选类名
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes); // exclude 属性 + spring.autoconfigure.exclude
checkExcludedClasses(configurations, exclusions); // 排除了一个非自动配置类会直接报错,防手滑
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations); // 大头:条件过滤,砍掉大半候选
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 2.7 的过渡形态:老的 spring.factories 和新的 AutoConfiguration.imports 两个来源都读
//(3.0 起 spring.factories 里的自动配置 key 被彻底移除,只剩 imports 文件)
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
private static class ConfigurationClassFilter {
private final AutoConfigurationMetadata autoConfigurationMetadata; // 编译期生成的条件元数据,免加载类就能判断
private final List<AutoConfigurationImportFilter> filters; // OnClassCondition / OnBeanCondition / OnWebApplicationCondition
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
// 批量匹配:一次传入全部候选,返回 boolean[],而不是逐类判断 —— 启动性能优化点
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null; // 条件不满足(如 @ConditionalOnClass 的类不在 classpath)直接置空
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
// ... 收集非 null 的存活者返回,省略
}
}
}原理串讲
从 SpringApplication.run(MyApp.class, args) 走一遍。
构造器先干三件事:
代码块收起展开
`WebApplicationType.deduceFromClasspath()` 检查 classpath 上有没有 Servlet/Reactive 的标志类来定应用类型;
两次 `getSpringFactoriesInstances` 从所有 jar 的 `META-INF/spring.factories` 里捞出 `ApplicationContextInitializer` 和 `ApplicationListener`;deduceMainApplicationClass() 翻异常栈帧找 main。
为什么用”看 classpath 有什么类”来推断类型?因为 SpringBoot 的哲学是”依赖即意图”——你引了 spring-boot-starter-web 就说明你要跑 Servlet 应用,不需要再写一行配置声明,这和后面 @ConditionalOnClass 是同一个思想的两次应用。
进入 run()。getRunListeners 再次走 SpringFactoriesLoader.loadFactoryNames,拿到 EventPublishingRunListener,此后启动的每个阶段(starting、environmentPrepared、contextPrepared、started、ready/failed)都是一次事件广播。
为什么要把启动流程做成事件驱动?因为启动早期容器还不存在,无法用 Bean 机制做扩展,事件是唯一能让第三方(比如加载 application.yml 的 EnvironmentPostProcessorApplicationListener、日志系统初始化)挂进启动流程的方式——prepareEnvironment 里那句 listeners.environmentPrepared(...) 就是配置文件被真正读取的时刻。
随后 createApplicationContext() 按构造器里推断的类型创建容器,prepareContext 把 Environment 塞入、跑完所有 Initializer,最后 load() 只注册主类这一个 BeanDefinition。
refreshContext 调 refresh() 进入 IOC 主干十二步,ConfigurationClassPostProcessor 在 invokeBeanFactoryPostProcessors 那一步解析主类上的 @SpringBootApplication,@ComponentScan 扫你的包,@EnableAutoConfiguration 引出自动配置。
自动配置这条线:@EnableAutoConfiguration 上的 @Import(AutoConfigurationImportSelector.class) 让解析器回调 selectImports,核心在 getAutoConfigurationEntry:
getCandidateConfigurations 从约定文件读出全量候选(2.7 同时读 spring.factories 和 AutoConfiguration.imports 两处),然后去重、剔除 exclude,最后 ConfigurationClassFilter.filter 用 OnClassCondition 等过滤器批量砍人。
为什么过滤要走 AutoConfigurationImportFilter 批量匹配,而不是等每个类解析时再逐个判断 @Conditional?因为候选有一百多个,逐个加载类字节码再解析注解太慢;
AutoConfigurationMetadata 是编译期就生成好的”类名到条件”清单(spring-autoconfigure-metadata.properties),过滤阶段只查字符串表就能淘汰大部分候选,被淘汰的类根本不会被 ClassLoader 加载。
还要注意它是 DeferredImportSelector:所有用户配置类处理完才轮到它,这保证了 @ConditionalOnMissingBean 看到的是”你已经定义了什么”,从而实现”用户配置永远压过自动配置”的覆盖语义。
设计取舍
- 构造器只收集不执行:Initializer/Listener 在 new SpringApplication 时加载,在 run 的对应时机才调用,收集与执行分离,顺序由
@Order控制。 spring.factories全量加载 + 按 ClassLoader 缓存:第一次读完所有 jar 后启动期内零 IO,代价是启动时不能动态增删扩展。- 条件过滤分两层:filter 阶段用编译期元数据快筛(不加载类),真正解析配置类时
@Conditional再精判,性能与准确性各管一段。 - 常见误区:以为
application.yml是容器加载的。实际它在prepareEnvironment广播事件时由监听器加载,早于容器创建。 - 常见误区:以为自动配置在 run 主线里执行。
selectImports实际发生在refresh()内部的 BeanFactoryPostProcessor 阶段,run 主线只负责把种子(主类)种进容器。