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) 走一遍。
构造器先干三件事:

代码块JAVA · 2 行收起展开
`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.ymlEnvironmentPostProcessorApplicationListener、日志系统初始化)挂进启动流程的方式——prepareEnvironment 里那句 listeners.environmentPrepared(...) 就是配置文件被真正读取的时刻。
随后 createApplicationContext() 按构造器里推断的类型创建容器,prepareContext 把 Environment 塞入、跑完所有 Initializer,最后 load() 只注册主类这一个 BeanDefinition。
refreshContextrefresh() 进入 IOC 主干十二步,ConfigurationClassPostProcessorinvokeBeanFactoryPostProcessors 那一步解析主类上的 @SpringBootApplication,@ComponentScan 扫你的包,@EnableAutoConfiguration 引出自动配置。

自动配置这条线:@EnableAutoConfiguration 上的 @Import(AutoConfigurationImportSelector.class) 让解析器回调 selectImports,核心在 getAutoConfigurationEntry:
getCandidateConfigurations 从约定文件读出全量候选(2.7 同时读 spring.factoriesAutoConfiguration.imports 两处),然后去重、剔除 exclude,最后 ConfigurationClassFilter.filterOnClassCondition 等过滤器批量砍人。
为什么过滤要走 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 主线只负责把种子(主类)种进容器。

延伸阅读