Spring容器refresh()启动流程与Bean生命周期源码解析
Spring 的 ApplicationContext 启动很多人背过八股文也知道大概有十二步但真到了排查问题的时候看着异常栈里的AbstractApplicationContext.refresh()往往一脸懵。我一个做后端的老朋友前几天就被线上一个启动失败折腾到凌晨日志里报BeanCurrentlyInCreationException他第一反应是不是有三级缓存吗为什么循环依赖还会挂结果查了半天才发现是自己多写了一个Async注解。这种问题如果你不了解 refresh() 内部每个阶段到底在干什么、后置处理器是在哪一步生效的基本只能靠猜。这篇文章我就把ApplicationContext接口体系、refresh()方法的完整执行链路、三级缓存机制、Bean 生命周期以及后置处理器的依赖关系全部串起来讲清楚。我尽量用我实际排障和读源码的经验来讲不会只贴源码而是告诉你每个阶段背后的设计意图、常见坑、以及你可以怎么利用这些扩展点做自己的功能。适合这几类人看准备 Spring 面试的 Java 开发者、系统学习 Spring 源码的初学者、以及遇到容器启动异常不知道怎么定位的工程人员。1. 先把背景讲清楚ApplicationContext 与 refresh() 的关系1.1 ApplicationContext 不是一个类而是一整套容器能力很多人把 ApplicationContext 理解成Spring 的大工厂这个说法没错但太笼统了。我从接口定义的角度重新梳理一下。ApplicationContext 是一个接口它同时继承了ListableBeanFactory、HierarchicalBeanFactory、MessageSource、ApplicationEventPublisher、ResourcePatternResolver等多个接口。也就是说一个 ApplicationContext 实现类同时具备以下能力作为 BeanFactory管理 bean 的定义、创建、依赖注入和生命周期作为 MessageSource提供国际化消息解析能力作为 ApplicationEventPublisher发布事件配合ApplicationListener实现观察者模式作为 ResourcePatternResolver支持从类路径、文件系统等位置加载资源。常见的实现类有ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、GenericWebApplicationContext等。它们虽然配置来源不同XML、注解、Web 环境但启动核心逻辑都在同一个父类里也就是AbstractApplicationContext。这一点很重要不管你是用 Spring Boot 还是手写 XML 配置最终都会走到AbstractApplicationContext.refresh()这个方法。所以当你面试被问到Spring 容器启动流程时不要上来就背 Spring Boot 的启动过程真正的底层核心就是 refresh()。Spring Boot 只是在 refresh() 之前做了一堆自动装配的准备工作并在onRefresh()阶段额外启动了内嵌 Web 容器。1.2 refresh() 为什么是容器启动的总开关看名字就知道refresh()是刷新的意思。官方的设计意图是这个方法可以多次调用每次会把当前的容器状态清掉基于已有的配置重新加载。但因为大多数应用在启动时只调用一次所以实际工作中它就是容器启动的入口。我画一条最简单的调用链AnnotationConfigApplicationContext 构造函数 - refresh() - prepareRefresh() // 容器状态准备 - obtainFreshBeanFactory() // 加载或刷新 BeanDefinition - prepareBeanFactory() // 配置容器标准特性 - postProcessBeanFactory() // 子类扩展点 - invokeBeanFactoryPostProcessors() // 执行 BeanFactoryPostProcessor - registerBeanPostProcessors() // 注册 BeanPostProcessor - initMessageSource() // 初始化国际化资源 - initApplicationEventMulticaster() // 初始化事件多播器 - onRefresh() // 模板方法子类扩展如 Spring Boot 启动 Web 容器 - registerListeners() // 注册监听器 - finishBeanFactoryInitialization() // 预实例化所有非懒加载单例 bean - finishRefresh() // 发布 ContextRefreshedEvent我用开餐厅来类比这套流程prepareRefresh 是开门前检查水电obtainFreshBeanFactory 是准备菜单和食材清单invokeBeanFactoryPostProcessors 是修改菜单比如根据新店员的习惯调整菜品registerBeanPostProcessors 是培训服务员让他们知道上菜前后要做什么finishBeanFactoryInitialization 是真正把每道菜做出来备着finishRefresh 是挂出营业中的牌子通知客人可以来了。这样类比你再看整条流程就清楚多了。下面我逐个阶段深入拆解。2. 逐层拆解 refresh() 的十二步启动流程2.1 准备与装载prepareRefresh() 和 obtainFreshBeanFactory()prepareRefresh()干的事比较基础但很关键设置容器的启动时间startupDate、活跃状态active、关闭状态closed初始化PropertySources也就是把环境变量、系统属性等配置源准备好校验必填的requiredProperties比如当前环境下是否缺少关键配置项初始化 earlyApplicationEvents用来暂存那些在监听器注册之前就发布的事件。我实际排查过一个案例某服务启动时报MissingRequiredPropertiesException就是因为有人把配置中心的某个 key 写成了必填项但没在配置文件中提供。这个错误就是在prepareRefresh()阶段抛出来的而不是在创建 bean 阶段。看异常栈的时候如果发现是在 refresh 早期挂的优先检查Environment相关的配置。obtainFreshBeanFactory()是把配置文件/配置类解析成 BeanDefinition 的核心方法。这里要区分一下ClassPathXmlApplicationContext在这个阶段会调用loadBeanDefinitions()解析 XML 文件AnnotationConfigApplicationContext则是在构造函数中通过AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner完成扫描和注册然后在refresh()时刷新容器。这里我不展开所有实现类但有一点你必须记住refresh()之后容器内部的BeanFactory实际是DefaultListableBeanFactory就已经持有了全部 BeanDefinition但此时 bean 还没有实例化。2.2 环境装配prepareBeanFactory() 与 postProcessBeanFactory()prepareBeanFactory()给底层的 BeanFactory 添加一些标准配置这些配置不依赖具体子类设置ClassLoader和表达式解析器注册默认的环境 beanenvironment、systemProperties、systemEnvironment注册ApplicationContextAwareProcessor让实现 Aware 接口的 bean 能拿到容器自身忽略EnvironmentAware、ApplicationEventPublisherAware、ResourceLoaderAware等接口的自动装配因为这些已经由 AwareProcessor 处理了注册ApplicationListenerDetector用于在 bean 创建后自动检测并注册监听器注册LoadTimeWeaver如果存在和Autowired注解的解析配置。你有没有遇到过在一个普通Component里直接注入ApplicationContext还注入成功的情况就是因为ApplicationContextAwareProcessor在这里发挥了作用它会在 bean 初始化阶段把容器回调给实现了ApplicationContextAware接口的对象。紧接着的postProcessBeanFactory()是个空实现方法留给子类去添加额外内容。比如GenericWebApplicationContext就利用这个方法注册了ServletContext相关的作用域和组件。Spring Boot 的ServletWebServerApplicationContext也通过重写这个方法注册了WebApplicationContextServletContextAwareProcessor。这一步骤体现了模板方法模式的典型用法固定流程由父类控制变化点交给子类扩展。2.3 核心扩展点invokeBeanFactoryPostProcessors() 和 registerBeanPostProcessors()这两个方法放到一起讲因为它们配合使用才能支撑起 Spring 的插件化扩展体系。invokeBeanFactoryPostProcessors()负责找到容器里所有的BeanFactoryPostProcessor并执行它们的postProcessBeanFactory()方法。这个步骤的执行顺序有讲究源码里有一段非常复杂的排序逻辑先执行通过编码方式手动注册的BeanDefinitionRegistryPostProcessor也就是实现了优先级接口PriorityOrdered的再执行实现了Ordered接口的然后执行剩余的最后执行普通的BeanFactoryPostProcessor。为什么这个顺序重要因为BeanDefinitionRegistryPostProcessor在运行时可以注册额外的 BeanDefinition而后续 bean 的创建完全依赖 BeanDefinition。如果让普通后置处理器先执行它面对的可能是不完整的 bean 定义集合。实际上 Spring 就是通过分层排序来保证扩展点的确定性。我举个例子很多框架的 starter 里会写一个BeanDefinitionRegistryPostProcessor来注册自己的核心组件比如 MyBatis 的MapperScannerConfigurer。它在这一步执行时直接把所有 Mapper 接口扫描成 BeanDefinition这样后面创建 bean 的阶段才能正常生产 Mapper 代理对象。registerBeanPostProcessors()是另一个经常被误解的点它只是注册后置处理器并不立即执行。真正的执行时机是 bean 实例化过程中AbstractAutowireCapableBeanFactory在创建每个 bean 时回调这些后置处理器。所以在容器启动早期你往容器里放一个BeanPostProcessor它只会被登记到beanPostProcessors列表里要等finishBeanFactoryInitialization阶段创建 bean 时才会真正生效。关于后置处理器在 Spring 容器启动流程中的依赖关系可以这样理解BeanFactoryPostProcessor 作用于 BeanDefinitionBeanPostProcessor 作用于 Bean 实例。前者属性改的是图纸后者改的是成品。两者都通过排序机制PriorityOrdered / Ordered / 无序来决定执行优先级。2.4 事件与国际化initMessageSource()、initApplicationEventMulticaster()、onRefresh()、registerListeners()这几个步骤是为容器运行提供辅助能力。initMessageSource()初始化消息源。如果容器里已经有用户自定义的MessageSourcebean就直接使用否则会注册一个DelegatingMessageSource作为默认实现。很多项目国际化不生效原因就是没有配置任何 MessageSource beanSpring 用了一个空实现兜底。initApplicationEventMulticaster()初始化事件广播器。Spring 事件机制的核心是ApplicationEventMulticaster接口默认实现是SimpleApplicationEventMulticaster。它主要负责维护监听器列表并在事件发布时逐个回调。如果你的容器里没定义这个 beanSpring 会创建默认实现并注册。onRefresh()是一个模板方法默认空实现Spring Boot 的ServletWebServerApplicationContext在这里创建并启动内嵌 Tomcat、Jetty 或 Undertow。所以 Spring Boot 应用真正把 Web 服务器跑起来的时机不在 main 方法里而是在 refresh() 的这一个阶段。registerListeners()把容器中所有实现了ApplicationListener接口的 bean 注册到事件多播器中。同时它还会把prepareRefresh()阶段暂存的 earlyApplicationEvents 在这里补发掉。有个细节值得注意如果某个事件是在registerListeners()之前发布的监听器还没注册事件不会丢失而是被暂存在earlyApplicationEvents集合里等监听器就绪后统一补发。但如果是registerListeners()之后发布的事件走的就是常规的事件广播流程不再有暂存机制。这解释了为什么很多人早期自定义事件时觉得有时候收到有时候收不到——很可能是在监听器注册完成之前发出去的。2.5 重头戏finishBeanFactoryInitialization() 和 finishRefresh()finishBeanFactoryInitialization()是整条流程里工作量最大、也最容易出问题的一步。它会遍历所有已经注册的 BeanDefinition实例化所有非懒加载的单例 bean。为了方便你理解我再说细一点判断 beanDefinition 是否抽象、是否非单例、是否懒加载符合条件的跳过创建 BeanFactoryPostProcessor 特殊标记的 bean比如BeanFactory自身真正创建 bean 时会经历实例化前回调InstantiationAwareBeanPostProcessor→ 构造器决定 → 实例化 → 属性填充依赖注入→ Aware 接口回调 →BeanPostProcessor的postProcessBeforeInitialization→PostConstruct/InitializingBean→BeanPostProcessor的postProcessAfterInitialization创建完成后放入容器一级缓存并做销毁回调注册。这里也是循环依赖问题最多发的地方。因为很多 bean 都在这个阶段集中实例化你中有我、我中有你就非常容易触发。最后的finishRefresh()会做几件收尾工作清理资源缓存初始化生命周期处理器LifecycleProcessor调用onRefresh()后启动已实现的 SmartLifecycle如果容器支持发布ContextRefreshedEvent事件通知所有监听器容器已经准备好注册LiveBeansView的 MBean。如果你在项目里监听ContextRefreshedEvent比如在ApplicationRunner或者ApplicationListenerContextRefreshedEvent里做初始化数据的逻辑你体会到的时间点就是整个容器已经就绪之后。这也是为什么很多人用这个事件做启动后执行一次的操作。3. 从源码到实战这些步骤背后的关键机制3.1 三级缓存机制与循环依赖的真相Spring 解决构造器循环依赖之外的问题靠的是三级缓存也就是容器内部维护了三个 map缓存名称作用一级缓存singletonObjects存储完全创建好的单例 bean二级缓存earlySingletonObjects存储已实例化但还未完成属性填充的早期 bean三级缓存singletonFactories存储 ObjectFactory用于生成早期的 bean 引用这里的关键点在于三级缓存存的是ObjectFactory而不是 bean 本身。为什么不是二级缓存直接存 bean因为 Spring 需要支持 AOP 代理的延迟生成。如果一个 bean 最终需要被 AOP 代理三级缓存中保存的工厂可以在其他 bean 引用这个早期实例时判断是否需要提前生成代理对象。这是 Spring 宁可多搞一层缓存也不提前生成代理的原因否则可能导致所有 bean 都提前被代理破坏了正常的生命周期。我再说清楚一点假设 A 和 B 互相依赖A 先被创建。A 实例化后放入三级缓存然后进行属性填充时发现需要 B于是去创建 B。B 实例化后放入三级缓存进行属性填充时发现需要 A于是从三级缓存中拿到 A 的 ObjectFactory调用getObject()得到 A 的早期引用放入二级缓存。B 拿到 A 的引用后完成创建并进入一级缓存。接着 A 继续创建从一级缓存拿到完全创建好的 B完成自己的属性填充。最终 A 创建完毕也进入一级缓存。但注意如果你用Async注解Spring 会通过AbstractAutoProxyCreator提前对 bean 做代理而 B 在创建时拿到的是 A 的早期引用这个引用在 A 被真正执行postProcessAfterInitialization之前就已经被缓存了B 持有的仍是原始 A 对象而不是最终代理对象于是 B 调用 A 的方法时异步增强不生效还会在后续触发校验异常。很多人在网上搜三级缓存原理背得滚瓜烂熟但遇到Async循环依赖还是不知道怎么排查。我想用我的经验提醒你碰到BeanCurrentlyInCreationException先想清楚依赖链是不是构造器注入是不是后置处理器创建了代理对象导致提前暴露的引用不完整然后再考虑加一个 Lazy这种缓解方案。3.2 Bean 生命周期中的后置处理器依赖关系Bean 生命周期和 BeanPostProcessor 的关系是面试和实战的高频交叉点。一个标准 bean 的创建过程是这样的实例化前InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()有机会直接返回一个代理对象替代正常创建流程实例化通过构造器或工厂方法创建对象实例化后MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition()解析注解元数据为后续依赖注入做准备InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()决定是否继续走自动属性填充属性填充InstantiationAwareBeanPostProcessor.postProcessProperties()执行Autowired、Resource、Value等注入初始化前BeanPostProcessor.postProcessBeforeInitialization()这里会触发 Aware 回调然后执行用户标注的PostConstruct方法初始化调用InitializingBean.afterPropertiesSet()或自定义 initMethod初始化后BeanPostProcessor.postProcessAfterInitialization()AOP 代理通常在这里生成使用阶段销毁触发PreDestroy、DisposableBean.destroy()、destroyMethod。后置处理器之间的依赖关系可以归纳为应用链模式它们按照排序依次包裹在 bean 创建流程外前一个处理器的输出是后一个处理器的输入。比如AutowiredAnnotationBeanPostProcessor负责Autowired注入AnnotationAwareAspectJAutoProxyCreator负责 AOP 代理AsyncAnnotationBeanPostProcessor负责Async处理。如果多个处理器都尝试修改同一个 bean 的行为执行顺序就变得至关重要这就是为什么 Spring 提供了PriorityOrdered和Ordered接口来排序。3.3 手写一个 mini refresh()理解容器启动的最小骨架为了让你彻底理解这套流程我简化出了一个最小可用版本。这里不追求完整源码而是让你看到骨架和事件顺序。public class MiniApplicationContext { private final DefaultListableBeanFactory beanFactory new DefaultListableBeanFactory(); private final MapString, Object singletonObjects new ConcurrentHashMap(); public void refresh() { // 1. 准备阶段 prepareRefresh(); // 2. 加载 bean 定义省略 XML/注解解析逻辑 obtainFreshBeanFactory(); // 3. 注册后置处理器 registerBeanPostProcessors(); // 4. 实例化非懒加载单例 bean finishBeanFactoryInitialization(); } private void finishBeanFactoryInitialization() { for (String beanName : beanFactory.getBeanDefinitionNames()) { Object bean createBean(beanName); singletonObjects.put(beanName, bean); } } private Object createBean(String beanName) { // 实例化 Object bean new Object(); // 属性填充 // 初始化回调省略 return bean; } private void registerBeanPostProcessors() { // 从 beanFactory 中查找 BeanPostProcessor 类型的 bean // 排序后放入列表后续每个 bean 创建时依次回调 // beanFactory.addBeanPostProcessor(...) } private void obtainFreshBeanFactory() { // 把配置解析成 BeanDefinition 并注册到 beanFactory // beanFactory.registerBeanDefinition(...) } private void prepareRefresh() { // 设置状态、初始化配置源 } }实际项目中你可以直接拿DefaultListableBeanFactory做最小容器的研究配合ClassPathBeanDefinitionScanner扫描配置类再用AnnotationConfigApplicationContext做对比能看到真容器多做了哪些事情。4. 实战复盘通过启动日志和断点定位容器初始化问题4.1 如何高效观察 Spring 启动过程想要真正理解 refresh()光看源码不够要把启动日志和断点结合起来。Spring 启动时有一条经典的日志顺序以 Spring Boot 为例Starting Application on ... Running with Spring Boot v... ... Root WebApplicationContext: initialization started in ... ... BeanFactoryPostProcessor 日志自动配置类注册阶段 ... Tomcat initialized with port(s): 8080 ... Root WebApplicationContext initialized in ... ms ... Started Application in ... seconds其中Root WebApplicationContext: initialization started这条日志出现在prepareRefresh()阶段Root WebApplicationContext initialized出现在finishRefresh()阶段。通过对比这两个时间点之间的耗时你能快速判断容器初始化是否成了瓶颈。如果你需要更细致的观察可以在AbstractApplicationContext.refresh()的各个方法调用处打断点或者在 IDEA 中使用 Evaluate Expression 查看beanFactory.getBeanDefinitionNames()的实时内容。我个人习惯在invokeBeanFactoryPostProcessors之前打一个条件断点过滤特定 beanName这样能快速确认某个自定义 BeanDefinition 是否已经注册。4.2 用 BeanFactoryPostProcessor 和 BeanPostProcessor 做扩展的实战案例我举一个实战案例一个多租户项目需要根据配置动态注册 DataSource 对应的SqlSessionFactoryBean。如果用BeanDefinitionRegistryPostProcessor核心逻辑是Component public class DynamicDataSourceRegister implements BeanDefinitionRegistryPostProcessor { Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // 动态注册多个数据源对应的 SqlSessionFactory BeanDefinitionBuilder builder BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class); builder.addPropertyValue(dataSource, dataSource()); builder.addPropertyValue(mapperLocations, new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/*.xml)); registry.registerBeanDefinition(sqlSessionFactory1, builder.getBeanDefinition()); } Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 通常不需要额外处理 } }这段代码能生效的根本原因就是 refresh() 里invokeBeanFactoryPostProcessors()在加载完 BeanDefinition 之后、实例化 bean 之前执行了它。再举个例子如果你想在某个 bean 初始化完成后自动做监控指标上报可以写一个BeanPostProcessorComponent public class MonitoringBeanPostProcessor implements BeanPostProcessor { Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof HealthCheckable) { MetricsRegistry.register(beanName, (HealthCheckable) bean); } return bean; } }这里的关键认知是MonitoringBeanPostProcessor自身在容器启动早期被注册进beanPostProcessors列表但它对所有其他 bean 的监控逻辑是在其他 bean 各自的创建过程中生效的。如果你在两阶段之间理解不透就很容易出现后置处理器写了但一直没执行的困惑。4.3 Spring Boot 的 refresh() 扩展自动配置如何被加载Spring Boot 和 Spring 的差异主要体现在 refresh() 前后。Spring Boot 通过SpringApplication.run()最终也会调用ConfigurableApplicationContext.refresh()但在调用之前做了这些事通过SpringBootApplication的组合注解启用自动配置SpringFactoriesLoader加载META-INF/spring.factories中的AutoConfiguration类通过ConditionalOnClass、ConditionalOnMissingBean等条件注解过滤自动配置类自动配置类本质上也是配置类会被解析成 BeanDefinition进入 refresh() 的标准流程。进入 refresh() 之后Spring Boot 最关键的动作是在onRefresh()阶段启动内嵌 Web 容器。你可以这样理解Spring Framework 的 refresh() 是做菜流程Spring Boot 往流程里塞了自动点单和自动摆桌。如果你深入研究过 Spring Boot 源码你会发现ServletWebServerApplicationContext.onRefresh()调用了createWebServer()。这个方法创建 Servlet 容器并注册 dispatcherServlet 过程。这也就解释了为什么 Spring Boot 项目里DispatcherServlet的初始化时机和ApplicationContext的事件发布时机密切相关。5. 常见问题与排查技巧实录5.1 循环依赖报错明明三级缓存能解决为什么还报错三级缓存只能解决单例 bean 属性注入的循环依赖以下情况依然会报错构造器注入循环依赖因为 bean 实例化时还没放入三级缓存依赖根本拿不到早期引用Async等代理增强引起的循环依赖前面分析过提前暴露的对象不是最终代理对象非单例作用域的 beanprototype循环依赖三级缓存不负责 prototypeDependsOn强制依赖顺序导致先创建的 bean 还没缓存。排查思路先看异常栈里提到了哪个 beanName然后在 IDE 里全局搜索该 bean 的引用关系。用Lazy在字段上注入可以延迟代理的创建但这不是根治办法长期看应该重构依赖方向。5.2 Bean 定义被覆盖allowBeanDefinitionOverriding 的坑Spring Boot 2.1 之后默认不允许 bean 定义覆盖除非设置spring.main.allow-bean-definition-overridingtrue。很多人扫描包时不小心同时加载了两个相同 beanName 的配置启动会直接报BeanDefinitionOverrideException。但有时候允许覆盖会掩盖问题两个 starter 都定义了同一个 bean但实现逻辑完全不一样启动后到底用的是哪个取决于加载顺序。这是一个很隐蔽的坑。我的建议是保持默认关闭覆盖遇到冲突时优先用Primary、ConditionalOnMissingBean或在配置里排除自动配置类。5.3 重复 refresh() 导致容器状态异常前面说过 refresh() 理论上可以多次调用。但在 Spring Boot 场景下同一个 context 如果重复 refresh内部组件比如内嵌服务器可能无法正常工作。我在测试代码里试过在同一个AnnotationConfigApplicationContext对象上连续调用两次 refresh()第二次启动会报各种状态异常。排查方法在AbstractApplicationContext里设置了synchronized (startupShutdownMonitor)锁如果出现启动卡死先用 jstack 看线程栈确认是否有其他线程也在操作容器。实际应用中极少有人主动重复 refresh但如果遇到多半是测试代码或者动态刷新配置时误触。故障现象通常是bean 创建了一部分另一部分拿到的是上一轮的旧实例容器状态不一致。规避方式很简单——每次需要新容器就 new 一个 context不要复用。5.4 监听器没生效、事件没触发的问题EventListener注解的方法能被调用依赖的是EventListenerMethodProcessor。这个类本身实现了SmartInitializingSingleton接口它会在单例 bean 预实例化阶段结束后扫描所有 bean把带EventListener的方法包装成ApplicationListenerMethodAdapter注册到事件多播器。所以你会发现一个现象如果某个EventListener所在的 bean 是懒加载的或者因为某种原因没有被实例化它的事件监听就不会生效。Spring Boot 应用里常见的原因是启动后主动调用了close()或者监听器所在 bean 被条件装配排除了。排查时可以查看启动日志里的 listener 注册情况或者在ApplicationEventMulticaster.getApplicationListeners()上打断点确认。5.5 常见问题速查表症状可能阶段常见原因解决思路MissingRequiredPropertiesExceptionprepareRefresh环境配置缺必填项检查 Environment 属性及配置中心BeanDefinitionOverrideExceptionobtainFreshBeanFactory / 后置处理器同名 BeanDefinition 冲突关闭覆盖开关或排查扫描路径BeanCurrentlyInCreationExceptionfinishBeanFactoryInitialization构造器循环依赖 / Async 场景加 Lazy、重构依赖或改为 setter 注入NoSuchBeanDefinitionException任何阶段BeanDefinition 未被加载检查扫描范围、条件注解、BeanDefinitionRegistryPostProcessorContextRefreshedEvent 没触发finishRefresh监听器注册失败或容器提前关闭检查 listener bean 是否被实例化、有没有多容器并存自定义后置处理器不执行registerBeanPostProcessors处理器本身没被注册或排序导致被跳过在处理器打断点看它是否进入 beanPostProcessors 列表我在实际项目中踩过的最深的一个坑是一个公共组件模块里的BeanPostProcessor被定义成内部类没有设置 public结果 Spring 扫描时静默跳过。启动不报错但那批增强逻辑一直没生效排查花了大半天。后来我提醒团队凡是全局后置处理器的类必须能被 Spring 正常扫描和实例化并且最好打日志确认注册数量。另外还有一个排查利器开启 Spring 的 debug 日志给org.springframework.beans.factory和org.springframework.context包设置TRACE级别能看到每一步 bean 的创建顺序和 postProcessor 的执行日志。这个日志量很大你最好配合启动参数定向输出到单独文件。最后分享一个我自己用来验证是否真正理解了 refresh()的小技巧给一个普通的Component同时实现BeanFactoryPostProcessor、BeanPostProcessor、ApplicationListenerContextRefreshedEvent三个接口然后观察这三个回调的执行顺序和日志打印顺序。跑一遍之后你会发现postProcessBeanFactory最先执行postProcessAfterInitialization在 bean 创建时执行onApplicationEvent最后执行。有了这种直观感受之后再读源码很多概念就一次性串起来了。如果你还能从这个组件里意识到一个 bean 承担多种角色时的副作用你对 Spring 容器启动流程就已经不再是背答案的水平了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →