
1. 从“启动特定代码”到“自动装配”SpringBoot启动流程的深度拆解很多刚开始接触SpringBoot的朋友都会对一个问题感到好奇我写的代码到底是在哪个时间点被SpringBoot加载并执行的比如我想在应用启动后立刻执行一些初始化操作比如加载缓存、建立数据库连接池、或者向某个服务注册自己这个代码应该写在哪里是写在一个普通的Component里还是实现某个接口更进一步为什么我们只需要在pom.xml里加一个spring-boot-starter-web依赖就能直接启动一个Web服务器而不用像以前Spring MVC那样去配置DispatcherServlet和web.xml这一切的魔法都源于SpringBoot的灵魂——启动流程与自动装配。今天我们不谈那些宽泛的概念就从“如何在SpringBoot启动时运行特定代码”这个最实际的需求切入一层层剥开SpringBoot启动过程的神秘面纱。你会发现理解了这个流程之前很多零散的知识点比如SpringBootApplication注解、SpringApplication.run()方法、甚至是面试常问的自动装配原理都会变得异常清晰和连贯。这对于解决实际开发中的启动顺序问题、依赖注入时机问题、以及进行深度定制化开发都至关重要。2. 启动时执行代码的四种主流姿势及其原理当你需要在SpringBoot应用启动完成后即ApplicationContext已完全刷新所有单例Bean已实例化立即执行一些逻辑时你有多种选择。每种方式背后都对应着Spring生命周期中不同的阶段。理解这些阶段的差异是写出健壮启动代码的关键。2.1 使用CommandLineRunner或ApplicationRunner接口这是最常用、也是最被推荐的方式。这两个接口的设计目的非常纯粹在Spring应用上下文准备就绪后但在应用开始接收外部请求对于Web应用或执行命令行逻辑之前运行一些特定的代码。如何使用你只需要创建一个Bean并实现其中一个接口即可。SpringBoot会自动发现并执行它们。import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.Arrays; Component public class MyStartupRunner implements CommandLineRunner { Override public void run(String... args) throws Exception { // 这里的args就是来自main方法的命令行参数 System.out.println(【CommandLineRunner】应用启动完成开始执行初始化任务...); System.out.println(命令行参数: Arrays.toString(args)); // 例如初始化缓存、预热数据、检查外部服务连通性 this.initCache(); } private void initCache() { // 模拟缓存预热 System.out.println(缓存预热完成。); } }ApplicationRunner接口与之类似但它提供了一个更丰富的ApplicationArguments对象来封装参数可以更方便地解析--optionvalue这种格式的参数。import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; Component public class MyAppRunner implements ApplicationRunner { Override public void run(ApplicationArguments args) throws Exception { System.out.println(【ApplicationRunner】启动参数解析:); System.out.println(非选项参数: args.getNonOptionArgs()); System.out.println(选项参数名: args.getOptionNames()); args.getOptionNames().forEach(name - System.out.println(name - args.getOptionValues(name)) ); } }执行顺序与原理时机在ApplicationContext的refresh()方法完成后SpringApplication.run()方法返回之前被调用。此时所有单例Bean都已实例化Autowired注入完成属性文件已加载环境已准备就绪。顺序如果有多个Runner你可以通过Order注解或实现Ordered接口来指定执行顺序数值越小优先级越高。为什么推荐这个因为它语义清晰专为“启动后任务”设计并且与Spring的生命周期完美集成。你不需要关心Bean的初始化顺序Spring保证在所有Bean准备好之后才调用它们。踩坑提示不要在CommandLineRunner或ApplicationRunner中执行耗时极长的阻塞操作这会导致应用启动时间变长。对于异步初始化任务考虑在Runner中提交到线程池执行。2.2 监听Spring事件ApplicationReadyEvent另一种灵活的方式是监听Spring的应用事件。Spring在启动过程中会发布一系列事件我们可以通过实现ApplicationListener接口或使用EventListener注解来监听特定事件。对于“启动完成”这个时机最相关的事件是ApplicationReadyEvent。import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; Component public class MyEventListener { EventListener(ApplicationReadyEvent.class) public void onApplicationReady(ApplicationReadyEvent event) { System.out.println(【EventListener】应用已完全就绪可以开始服务请求了); // 此时可以安全地执行需要依赖其他Bean的初始化操作 // 例如通过 event.getApplicationContext() 获取上下文进行一些操作 } }与CommandLineRunner的区别时机几乎相同ApplicationReadyEvent的发布时机与CommandLineRunner的执行时机非常接近都在应用完全就绪后。设计哲学不同Runner是“主动执行一段代码”而事件监听是“被动响应一个状态变化”。事件机制更解耦允许多个监听器对同一事件做出反应且监听器之间无需知道彼此的存在。细微差别严格来说ApplicationReadyEvent的发布是在所有Runner执行完毕之后。所以如果你有极其严格的顺序要求比如A必须在B之后需要注意这一点。2.3PostConstruct注解与InitializingBean接口这两个是Spring框架原生提供的Bean生命周期回调机制并非SpringBoot特有。它们作用于单个Bean的初始化阶段。PostConstruct注解由JSR-250规范定义标注在方法上。该方法会在Bean的依赖注入完成之后afterPropertiesSet()方法之前被调用。import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; Component public class MyService { PostConstruct public void init() { System.out.println(【PostConstruct】MyService Bean的依赖注入已完成正在初始化...); // 适合初始化该Bean自身的内部状态 } }InitializingBean接口Spring框架的接口需要实现afterPropertiesSet()方法。其执行时机在PostConstruct之后。import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; Component public class AnotherService implements InitializingBean { Override public void afterPropertiesSet() throws Exception { System.out.println(【InitializingBean】AnotherService属性设置完毕。); } }重要限制与选择这两种方式都是在单个Bean的生命周期内被调用。此时不能保证其他Bean已经初始化完成。如果你在PostConstruct方法里通过Autowired注入了一个OtherService并调用其方法这通常是安全的因为依赖注入已经完成。但如果你期望所有Bean尤其是那些没有直接依赖关系的Bean都完成初始化后再执行那么CommandLineRunner或监听ApplicationReadyEvent是更安全的选择。2.4 在main方法或Bean方法中直接执行有些初学者可能会尝试在main方法里调用SpringApplication.run()之后直接写代码或者在Bean定义的方法体内添加逻辑。SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context SpringApplication.run(DemoApplication.class, args); // 【不推荐】在这里直接写初始化代码 System.out.println(SpringApplication.run() 刚执行完。); // 问题此时部分Bean可能还未完成延迟初始化(Lazy Init)Web服务器可能还未完全启动。 } }Configuration public class AppConfig { Bean public SomeBean someBean() { SomeBean bean new SomeBean(); // 【不推荐】在这里执行复杂的初始化或依赖其他Bean的操作 bean.doSomething(); // 此时OtherBean可能还未被创建 return bean; } }为什么不推荐因为时机不可控。在main方法中SpringApplication.run()返回的ApplicationContext虽然已经创建但某些Bean特别是标记了Lazy的可能还未实例化对于Web应用内嵌的Tomcat/Netty服务器可能还在启动过程中。在Bean方法中你无法保证该Bean创建时它所依赖的其他Bean尤其是通过方法调用而非注入的已经存在于容器中。这会导致难以调试的NullPointerException或状态不一致问题。结论对于需要在所有Bean就绪后执行的全局性启动任务优先使用CommandLineRunner/ApplicationRunner或监听ApplicationReadyEvent。3. 深入SpringApplication.run()启动流程全景图理解了“在哪里写代码”我们再来看看SpringBoot是如何把这些代码“跑起来”的。SpringApplication.run()是这一切的起点。我们可以将其启动流程划分为几个关键阶段这有助于我们在不同阶段进行拦截或定制。3.1 阶段一初始化与推断当你调用SpringApplication.run(MyApplication.class, args)时首先会创建一个SpringApplication实例。在这个初始化过程中SpringBoot会做几件重要的事推断应用类型根据类路径上的依赖判断你是要创建一个普通的非Web应用ApplicationType.NONE、一个Servlet-based的Web应用ApplicationType.SERVLET如使用Tomcat还是一个响应式Web应用ApplicationType.REACTIVE如使用Netty。这决定了后续如何创建和配置ApplicationContext。加载“应用上下文初始化器”从META-INF/spring.factories文件SpringBoot 2.7之前或META-INF/spring/org.springframework.boot.SpringApplication目录SpringBoot 2.7支持application.properties风格的spring.factories中加载ApplicationContextInitializer。这些初始化器可以在ConfigurableApplicationContext刷新refresh之前对其做一些预处理比如设置环境属性、注册Bean定义等。加载“应用监听器”同样从上述位置加载ApplicationListener。这些监听器用于接收我们在第2.2节提到的各种应用事件如ApplicationStartingEvent、ApplicationPreparedEvent等。SpringBoot自身的很多功能如日志初始化、配置加载都是通过内置的监听器实现的。推断主配置类通常就是你传入的MyApplication.class即标注了SpringBootApplication的那个类。3.2 阶段二运行与事件驱动run()方法的核心是一个典型的事件发布-监听模型。它按顺序发布关键事件驱动启动流程向前推进。// 这是一个高度简化的逻辑流程帮助你理解 public ConfigurableApplicationContext run(String... args) { // 1. 发布 ApplicationStartingEvent // 此时监听器已就位但环境Environment和上下文Context都还未创建。 // 2. 准备环境Environment // 加载 application.properties/yml处理命令行参数激活 profiles如 --spring.profiles.activedev。 // 发布 ApplicationEnvironmentPreparedEvent监听器可以在此修改环境配置。 // 3. 创建应用上下文ApplicationContext // 根据第一步推断的应用类型创建对应的上下文实例例如 AnnotationConfigServletWebServerApplicationContext。 // 4. 准备上下文 // 调用之前加载的 ApplicationContextInitializer 来初始化上下文。 // 加载主配置类你的 SpringBootApplication 类和其他源通过 SpringApplication.setSources。 // 发布 ApplicationContextInitializedEvent。 // 5. 刷新上下文核心中的核心 // 调用上下文的 refresh() 方法。这是Spring框架的核心它 // - 准备BeanFactory后置处理器注册等。 // - 调用BeanFactoryPostProcessor例如处理 PropertySource解析配置属性到Bean定义。 // - 注册并调用BeanPostProcessor。 // - 初始化消息源、事件广播器等。 // - 注册Bean定义扫描 Component, Service 等。 // - 完成BeanFactory的初始化实例化所有非懒加载的单例Bean触发 PostConstruct, InitializingBean。 // - 发布 ContextRefreshedEvent。 // - 对于Web应用在这一步的最后会启动内嵌的Web服务器Tomcat/Netty/Jetty。 // 6. 发布 ApplicationPreparedEvent // 上下文已刷新但 CommandLineRunner 和 ApplicationRunner 还未被调用。 // 7. 调用 CommandLineRunner 和 ApplicationRunner // 这就是我们第二节写的代码被执行的地方 // 8. 发布 ApplicationReadyEvent 或 ApplicationFailedEvent // 如果启动成功发布 ApplicationReadyEvent如果发生异常发布 ApplicationFailedEvent。 // 9. 返回运行中的 ApplicationContext return context; }这个流程解释了为什么你的CommandLineRunner能安全地使用所有Bean——因为它是在第7步即所有单例Bean实例化第5步之后才被调用的。3.3 阶段三Web服务器的启动与端口监听对于Web应用内嵌服务器的启动是刷新上下文第5步的一部分。具体来说是在refresh()方法的onRefresh()钩子中触发的。以Servlet Web应用为例SpringBoot通过自动装配在上下文中创建了一个ServletWebServerApplicationContext。在onRefresh()方法中这个上下文会调用createWebServer()方法。该方法从BeanFactory中获取一个ServletWebServerFactory例如TomcatServletWebServerFactory。工厂类创建WebServer例如TomcatWebServer并初始化。在这个过程中它会将Spring MVC的核心DispatcherServlet注册进去。最后启动这个WebServer开始监听你在application.properties中配置的端口默认为8080。一个常见的坑如果你的启动代码比如一个PostConstruct方法执行时间过长它会阻塞主线程而主线程正在执行refresh()方法这会导致Web服务器无法及时启动从外部看就是应用启动“卡住”了。因此对于耗时任务务必使用异步方式。4. 自动装配魔法背后的EnableAutoConfiguration现在让我们回答开头提出的另一个问题为什么加了spring-boot-starter-webWeb服务器就自动有了这就是自动装配Auto-Configuration的功劳。而这一切的开关就在SpringBootApplication注解上。SpringBootApplication是一个复合注解它核心包含三个SpringBootConfiguration表明这是一个SpringBoot的配置类。ComponentScan开启组件扫描自动发现并注册标注了Component,Service,Repository,Controller等的Bean。EnableAutoConfiguration启用自动配置的魔法钥匙。4.1EnableAutoConfiguration的工作原理这个注解会导入一个AutoConfigurationImportSelector。这个选择器是自动装配的大脑它的工作流程如下收集候选配置它扫描所有jar包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件SpringBoot 2.7或META-INF/spring.factories文件旧版读取其中EnableAutoConfiguration键对应的全限定类名列表。这些类就是一个个“自动配置类”。过滤根据当前项目的类路径Classpath、已存在的Bean定义、环境属性Conditional注解等条件过滤掉不满足条件的配置类。这是通过大量的ConditionalOnClass,ConditionalOnMissingBean,ConditionalOnProperty等条件注解实现的。加载与生效将过滤后剩余的自动配置类加载到Spring上下文中。这些配置类本身也是普通的Configuration类它们内部使用Bean方法定义了一系列的Bean。举个例子spring-boot-autoconfigurejar包下的/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中有一行是org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration。当你的类路径下有Servlet API和相关Web依赖时这个配置类就会被激活。4.2 解剖一个自动配置类DispatcherServletAutoConfiguration我们来看一下这个类的简化逻辑AutoConfiguration // 标明这是一个自动配置类 ConditionalOnWebApplication(type Type.SERVLET) // 条件1必须是Servlet Web应用 ConditionalOnClass(DispatcherServlet.class) // 条件2类路径下必须有DispatcherServlet类 EnableConfigurationProperties(WebMvcProperties.class) // 引入配置属性绑定 public class DispatcherServletAutoConfiguration { Bean ConditionalOnMissingBean // 条件3只有当用户没有自己定义DispatcherServlet Bean时这个才生效 public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet new DispatcherServlet(); // 根据 application.properties 中的 spring.mvc.* 配置设置DispatcherServlet属性 dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); // ... 其他配置 return dispatcherServlet; } // 还会注册其他相关的Bean如 DispatcherServletRegistrationBean }看到了吗自动配置的本质是“条件化配置”。它说“如果这是一个Servlet Web应用ConditionalOnWebApplication并且类路径下有DispatcherServletConditionalOnClass而且用户自己没有提供DispatcherServlet的BeanConditionalOnMissingBean那么我就自动帮你配一个并且把application.properties里spring.mvc开头的配置应用上去。”这完美解释了“约定大于配置”的理念。你只要引入starter满足条件我就给你一套能跑起来的最佳实践配置。如果你想定制很简单自己写一个Bean来覆盖它就行。4.3 如何查看和调试自动装配启动时查看在application.properties中设置debugtrue启动时控制台会打印一份自动配置报告清晰地列出哪些配置类生效Positive matches、哪些未生效Negative matches及其原因。源码查看在IDE中直接查看spring-boot-autoconfigure模块下的META-INF/spring目录是学习自动配置最好的资料。5. 定制化启动流程高级玩家必备理解了标准流程我们就可以在关键时刻“插手”实现定制化需求。5.1 自定义ApplicationRunner或CommandLineRunner这已经是最常见的定制了。你可以创建多个Runner并通过Order控制顺序将不同的初始化任务模块化。5.2 自定义ApplicationListener监听更早期的事件如果你想在环境准备阶段就介入比如根据特定的环境变量动态修改配置可以监听ApplicationEnvironmentPreparedEvent。import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import java.util.HashMap; public class CustomEnvironmentListener implements ApplicationListenerApplicationEnvironmentPreparedEvent { Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment event.getEnvironment(); // 在配置文件加载后可以动态添加或覆盖属性 HashMapString, Object customMap new HashMap(); customMap.put(custom.property, value-added-by-listener); environment.getPropertySources().addLast( new MapPropertySource(myCustomSource, customMap) ); System.out.println(自定义环境监听器执行添加了 custom.property); } }要让这个监听器生效你需要将其注册。传统方式是在META-INF/spring.factories中配置SpringBoot 2.7 推荐在META-INF/spring/org.springframework.boot.SpringApplication目录下创建application.properties文件内容为org.springframework.context.ApplicationListenercom.yourpackage.CustomEnvironmentListener5.3 使用ApplicationContextInitializer这个接口允许你在ApplicationContext刷新refresh之前对其BeanFactory进行编程式配置。例如提前注册一些特殊的Bean定义。import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; public class CustomContextInitializer implements ApplicationContextInitializerConfigurableApplicationContext { Override public void initialize(ConfigurableApplicationContext applicationContext) { // 可以在这里获取到 Environment // 也可以向 BeanFactory 中注册一些特殊的 BeanDefinition System.out.println(自定义上下文初始化器执行); // 例如手动注册一个Bean定义 // BeanDefinitionBuilder builder BeanDefinitionBuilder.genericBeanDefinition(MySpecialBean.class); // applicationContext.getBeanFactory().registerBeanDefinition(mySpecialBean, builder.getBeanDefinition()); } }注册方式与自定义ApplicationListener类似通过spring.factories或application.properties文件org.springframework.context.ApplicationContextInitializercom.yourpackage.CustomContextInitializer5.4 自定义SpringApplication实例如果你需要更全面的控制可以在main方法中创建SpringApplication实例并进行一系列设置然后再调用run。SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication app new SpringApplication(DemoApplication.class); // 关闭Banner app.setBannerMode(Banner.Mode.OFF); // 添加自定义的初始化器也可以在这里加替代spring.factories配置 app.addInitializers(new CustomContextInitializer()); // 添加自定义的监听器 app.addListeners(new CustomEnvironmentListener()); // 设置额外的默认属性 app.setDefaultProperties(Collections.singletonMap(default.prop, true)); // 最后运行 ConfigurableApplicationContext context app.run(args); } }这种方式给了你最大的灵活性但通常用于比较特殊的场景比如需要集成非Spring标准的框架。6. 实战避坑启动流程中的典型问题与排查理论结合实践下面分享几个在启动流程中容易遇到的“坑”及其排查思路。6.1 Bean创建顺序导致的依赖注入失败问题场景在MyService的PostConstruct方法中调用了Autowired的OtherService的一个方法但启动时报NullPointerException或Bean找不到。根因分析虽然Autowired能保证在MyService的PostConstruct被调用时OtherService的引用已经注入但如果OtherService自身的PostConstruct方法还没有执行即其初始化逻辑未完成你调用它的业务方法可能会因为内部状态未准备就绪而失败。更复杂的情况是循环依赖。解决方案避免在PostConstruct中进行强依赖的跨Bean复杂初始化。将启动逻辑移至CommandLineRunner或ApplicationReadyEvent监听器中。使用DependsOn注解明确指定Bean的创建顺序。Component DependsOn(otherService) public class MyService { ... }。但这会增加耦合应谨慎使用。解决循环依赖检查是否出现了A依赖BB又依赖A的情况。优先通过重构代码如提取公共逻辑到第三个类C来打破循环。如果必须存在确保使用Setter注入而非构造器注入因为Spring对Setter注入的循环依赖有解决能力三级缓存。6.2 配置属性Value在PostConstruct中为null问题场景在Bean的PostConstruct方法中使用Value(${some.property})注入的属性值是null或默认值。根因分析Value的注入发生在Bean生命周期的“属性填充”阶段早于PostConstruct。如果它为null通常是因为属性键some.property在任何一个PropertySource环境变量、系统属性、application.properties、application.yml等中都不存在且没有默认值。配置文件的加载顺序问题。例如你通过PropertySource引入了一个自定义文件但其加载时机晚于当前Bean的创建。排查与解决检查拼写和配置文件位置。在Value中设置默认值Value(${some.property:defaultValue})。在CommandLineRunner中打印所有属性确认属性已正确加载environment.getProperty(some.property)。确保自定义的PropertySource所在的配置类被正确扫描和加载。6.3 Web服务器端口被占用或启动失败问题场景应用启动日志显示Web服务器启动失败提示“Port 8080 was already in use”。根因分析端口冲突或者服务器在启动过程中遇到其他错误如SSL证书配置错误。解决方案换端口在application.properties中设置server.port8081。随机端口设置server.port0SpringBoot会分配一个随机可用端口。启动后可以通过environment.getProperty(local.server.port)获取实际端口这在微服务测试中很有用。检查日志查看更详细的错误堆栈。如果是SSL等问题需检查server.ssl.*相关配置。使用WebServerFactoryCustomizer定制如果你需要更复杂的服务器配置如线程池、连接器可以实现这个接口。import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.stereotype.Component; Component public class MyTomcatCustomizer implements WebServerFactoryCustomizerTomcatServletWebServerFactory { Override public void customize(TomcatServletWebServerFactory factory) { // 定制化Tomcat配置 factory.addConnectorCustomizers(connector - { // 设置最大连接数等 }); factory.setContextPath(/api); // 设置上下文路径 } }6.4 启动速度优化延迟初始化与组件扫描SpringBoot 2.2 引入了全局延迟初始化特性。设置spring.main.lazy-initializationtrue后所有Bean都将延迟创建直到第一次被请求时。这可以显著加快启动速度因为启动时只创建必要的Bean如配置类、BeanPostProcessor等。但需要注意副作用第一次请求的响应时间会变长因为需要等待Bean创建和初始化。不适用于所有Bean某些Bean如CommandLineRunner、Scheduled任务、EventListener方法必须在启动时初始化才能正常工作。对于这些Bean即使开启了全局延迟初始化也需要在类上添加Lazy(false)来强制立即初始化。谨慎使用在生产环境中需要权衡启动速度和首次请求延迟。通常更推荐优化组件扫描路径来提速。优化组件扫描SpringBootApplication默认扫描主类所在包及其子包。如果项目很大不必要的扫描会拖慢启动。SpringBootApplication(scanBasePackages {com.yourcompany.core, com.yourcompany.web}) // 或者排除特定包 // SpringBootApplication(exclude {SomeAutoConfiguration.class}) public class DemoApplication { ... }7. 从原理到面试如何回答“SpringBoot启动流程”如果你理解了前面的内容面对“谈谈SpringBoot的启动流程”或“SpringBoot自动装配原理”这类面试题就可以从容组织答案了。一个清晰的回答结构可以是总起SpringBoot通过SpringApplication.run()方法启动其核心是Spring容器的初始化refresh()和自动装配机制。流程简述初始化SpringApplication推断应用类型加载ApplicationContextInitializer和ApplicationListener。运行run()方法这是一个事件驱动流程发布StartingEvent- 准备环境加载配置- 创建上下文 - 准备上下文执行初始化器-刷新上下文核心实例化单例Bean启动Web服务器- 调用CommandLineRunner和ApplicationRunner- 发布ReadyEvent。自动装配详解由EnableAutoConfiguration触发。AutoConfigurationImportSelector从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports读取所有自动配置类。根据类路径、已有Bean、配置属性等条件ConditionalOnXxx进行过滤只加载符合条件的配置类。这些配置类中定义的Bean方法在满足ConditionalOnMissingBean等条件时会向容器注册相应的Bean从而完成“开箱即用”的配置。结合实际可以举一个具体的例子比如spring-boot-starter-web如何自动配置DispatcherServlet和Tomcat。扩展如果需要定制启动过程可以通过实现Runner、监听事件、实现ApplicationContextInitializer或自定义SpringApplication等方式介入。记住面试官不仅想听你背流程更想听到你的理解比如“为什么CommandLineRunner里可以用所有Bean”、“自动装配是如何做到不冲突的”。结合本文的讲解你完全可以给出令人满意的答案。理解启动流程和自动装配是掌握SpringBoot框架精髓的第一步也是解决复杂启动类问题的基石。