SpringApplication.run() 严格按7步执行:推断应用类型、设置初始化器和监听器、准备环境、创建并刷新上下文、执行Runner;其中refreshContext()是核心,调用AbstractApplicationContext.refresh()完成BeanFactory构建、后置处理器注册与Bean实例化。

SpringApplication.run() 执行了哪些关键步骤
它不是简单地 new 一个上下文再 refresh,而是按严格顺序做了 7 个核心动作:推断应用类型、设置初始化器和监听器、准备环境、创建并刷新上下文、执行 Runner。其中 refreshContext() 是真正的“启动心脏”,里面调用 AbstractApplicationContext.refresh() 完成 BeanFactory 构建、后置处理器注册、Bean 实例化等。
常见错误现象:ApplicationContext 已创建但 @Component 没被扫描到——大概率是 prepareEnvironment() 阶段没加载对 spring.factories,或主类包路径太深导致组件扫描漏掉子包。
- 主类必须在最外层包(如
com.example.demo),否则默认扫描会跳过更深层的@Service -
SpringApplication.setWebApplicationType(WebApplicationType.NONE)会跳过内嵌容器初始化,适合纯批处理场景 - 自定义
ApplicationContextInitializer在prepareContext()中执行,早于 Bean 定义加载,不能依赖已注入的 Bean
为什么 run() 启动慢?几个典型瓶颈点
启动耗时通常卡在三处:环境准备(尤其是读取大量 application.yml 或远程配置中心)、Bean 创建(特别是带复杂初始化逻辑的 @PostConstruct 或 InitializingBean.afterPropertiesSet())、Runner 执行(CommandLineRunner 或 ApplicationRunner 阻塞)。
性能影响明显的情况:@Bean 方法里调用外部 HTTP 接口、JDBC 连接池预热超时、Lombok 的 @Data + 大量字段触发 getter/setter 字节码增强延迟。
- 加
logging.level.org.springframework.boot=DEBUG可看到每个阶段耗时,重点关注Refreshing web server context和Started Application in X seconds之间的 gap - 用
spring.main.lazy-initialization=true延迟非必需 Bean 初始化,但注意@EventListener(ContextRefreshedEvent.class)会失效 - 避免在
static块或@PostConstruct中做 IO 操作;改用@EventListener(ApplicationReadyEvent.class)异步触发
refresh() 里 BeanFactoryPostProcessor 和 BeanPostProcessor 的执行时机差异
前者在 Bean 定义加载后、实例化前修改 BeanDefinition(比如 @Configuration 类解析、@PropertySource 注入);后者在 Bean 实例化后、初始化前后介入(比如 AOP 代理生成、@Value 注入)。
容易踩的坑:BeanPostProcessor 实现类本身是 Bean,必须在所有其他 Bean 创建前就注册好——所以 Spring Boot 会把它们从 spring.factories 提前加载,并在 invokeBeanFactoryPostProcessors() 阶段就完成注册。
- 自定义
BeanFactoryPostProcessor不要依赖任何普通 Bean(包括Environment,要用ConfigurableListableBeanFactory.getBean(Environment.class)安全获取) -
BeanPostProcessor的postProcessBeforeInitialization()无法修改 final 字段,@Value注入在此阶段完成 - 若用了
@Profile("dev")的BeanPostProcessor,但启动时没激活该 profile,则它不会生效,也不会报错
如何安全地在启动过程中获取 Environment 或 ApplicationContext
不能在 static 上下文或构造器里直接拿,因为此时上下文还没创建完。唯一可靠时机是 ApplicationContextAware.setApplicationContext() 或 @Autowired ApplicationContext 注入——但仅限于普通 Bean 内部使用。
如果要在 SpringApplicationRunListener 或 ApplicationContextInitializer 中访问,只能通过传参方式:比如 prepareEnvironment() 传入 ConfigurableEnvironment,prepareContext() 传入 ConfigurableApplicationContext。
- 在
ApplicationRunner中可放心用@Autowired Environment,此时上下文已 refresh 完毕 - 不要在
static块或main()方法里调用ApplicationContext.getBean()—— 此时run()还没返回,上下文对象都不存在 -
ApplicationContext.getEnvironment().getProperty("xxx")返回 null 不代表配置不存在,可能是类型转换失败,建议用getProperty("xxx", String.class)显式指定类型










