深入Spring核心:Bean生命周期、三级缓存与请求执行流程全解析
2026/9/9 6:14:11 网站建设 项目流程

做 Java 开发这么多年,Spring 始终是绕不过去的一层。上周团队新同事在排查一个 Bean 初始化顺序问题时,盯着调试窗口问我:"这个 BeanPostProcessor 到底是什么时候被调用的?为什么我在 postProcessBeforeInitialization 里打的日志有时看不到?" 我想了想,其实很多人在日常业务开发里对 Spring 的认知停留在"标注解、放容器、拿来用"的层面,真要解释 IoC 容器内部如何把一个普通类变成受管 Bean、中间经过哪几个阶段、请求又是怎么穿透整个框架到达 Controller 的,一时半会儿还真说不利索。

这也是我写下这篇文章的原因。标题里的三个词——原理、生命周期、执行流程——其实对应了三条必须理清的线索:容器如何构建对象、对象在容器内经历什么、应用运行后请求如何流转。把这三条线串起来,无论你是准备 Spring 面试,还是做通用组件封装,又或是正在被“Bean 还没初始化完就被拿去注入”这类问题折磨,都会轻松很多。下面我会按源码路径去拆,尽量不堆结论,说清楚每个设计"为什么这么做"。

1. IoC容器的本质:BeanFactory与ApplicationContext,谁在真正管理Bean?

1.1 IoC不只是"设计模式课"里的概念

很多教程喜欢把控制反转解释成一种设计思想:对象不再自己 new 依赖,而是把创建和管理的权利交给外部容器。这话没错,但太抽象了。我们换一个更直白的视角——没有 IoC 时,你的业务代码里到处是new UserService()new UserDao(),一旦 UserDao 构造方法变了,所有调用点都要跟着改。有了 IoC 容器后,你只需要告诉容器"UserService 需要 UserDao",容器就会在合适的时机把 UserDao 创建好、注入进去。

这里的控制权反转,反转的其实是"对象和依赖的组装动作"。而依赖注入(Dependency Injection)只是实现 IoC 的一种方式,不是 IoC 本身。Spring 里常见的做法是:构造器注入、Setter 注入、字段注入,本质上都是在容器内部帮你完成"找人、搭线"的动作。

我个人建议大家不要把 IoC 说得太玄。面试时被问到 IoC 和 DI 的区别,最加分的回答就是一句话:IoC 是一种设计思想,DI 是 Spring 实现这种思想的手段之一,思想本身大于某个具体框架。

1.2 BeanFactory:容器最底层的契约

在 Spring 源码里,有一个容易被忽略的接口叫BeanFactory,它才是 IoC 容器的真正地基。ApplicationContext虽然是我们日常中最常打交道的入口,但底层真正负责创建、缓存、管理 Bean 的,是它持有的DefaultListableBeanFactory

BeanFactory接口定义了最基本的容器能力:

  • getBean(String name):按名称获取 Bean,这是触发实例化的核心入口。
  • containsBean(String name):判断容器是否存在某个 Bean。
  • isSingleton(String name)/isPrototype(String name):判断作用域。
  • getType(String name):获取 Bean 类型。

这些方法有一个共同特点:它们都在"使用者视角"描述容器。真正创建 Bean 的逻辑,藏在AbstractBeanFactoryAbstractAutowireCapableBeanFactory这些实现类里。

有一个很重要的概念容易搞混:BeanFactory默认是懒加载的,也就是说你调getBean()时它才去创建对象;而ApplicationContext在启动时就会把大部分单例 Bean 预先创建好。后者之所以能做到"启动即就绪",靠的是启动流程里主动调用了一次getBean,并不是说BeanFactory本身改变了策略。

1.3 ApplicationContext:在BeanFactory之上叠了什么

ApplicationContext接口继承了ListableBeanFactoryHierarchicalBeanFactory,所以它首先是一个 BeanFactory。但它还做了几件很重要的事:

  • 继承了MessageSource,提供国际化消息查找能力。
  • 继承了ApplicationEventPublisher,提供事件发布能力。
  • 继承了EnvironmentCapable,提供环境配置(@Value背后依赖它)。
  • 默认预加载单例 Bean,而不仅仅是达到getBean时才创建。
  • 提供ResourceLoader能力,方便加载资源文件。

这意味着,如果你写的代码只是单纯依赖BeanFactory,那应用启动后可能一个业务 Bean 都没创建,直到有人调用getBean;而如果你使用ApplicationContext,它在refresh()过程中会执行finishBeanFactoryInitialization(),把所有非懒加载的单例 Bean 都实例化一遍。

所以在实际开发里,我们几乎不会直接用BeanFactory写业务代码,但看源码、写扩展、理解框架启动逻辑时,必须清楚:ApplicationContext 是门面,BeanFactory 才是干活的人

1.4 BeanDefinition:生命周期开始前的地基

在讨论 Bean 实例化之前,还有一个东西必须先讲清楚:BeanDefinition。它翻译过来就是"Bean 的定义",容器里存的其实不是一个一个的对象,而是一份一份的"对象简历"。

这份简历里写明了:

  • 类的全限定名是什么
  • 是单例还是原型(scope)
  • 是否需要懒加载(lazy-init)
  • 构造器参数有哪些
  • 属性值有哪些
  • 初始化方法名(init-method)
  • 销毁方法名(destroy-method)

Spring 的注解配置最终也需要转成 BeanDefinition。你在类上标了@ComponentClassPathBeanDefinitionScanner会扫描到它,然后注册成一个ScannedGenericBeanDefinition;你写了@BeanConfigurationClassPostProcessor会把它解析成ConfigurationClassBeanDefinition。也就是说,没有 BeanDefinition,就没有后面的所有生命周期

理解了这一点,你就能明白为什么BeanFactoryPostProcessor能修改 Bean 的属性或作用域——因为它操作的是 BeanDefinition,是在对象还没实例化之前修改"简历"。而BeanPostProcessor操作的是已经创建好的对象实例,两者阶段完全不同。这个区别在面试和实际扩展中都非常重要。

2. 从源码链路拆解Bean生命周期:那十来个回调点,到底谁先谁后?

2.1 生命周期不是"八股文",是容器里真实发生的步骤

很多面试题答案喜欢把 Bean 生命周期概括成"实例化、属性赋值、初始化、销毁"四个阶段。抽象得没错,但如果你想靠这个回答征服面试官,大概率不够。因为 Spring 在四个阶段之间塞了大量的扩展点,每个扩展点的触发顺序、依赖关系、设计意图,才是真正值得展开的部分。

把一个 Bean 从无到有到销毁,按源码主链可以拆成以下阶段:

阶段关键操作说明
实例化createBeanInstance通过构造器创建对象,注意此时属性还没赋值
属性填充populateBean@Autowired@Value、XML 里的 property 等值填进去
Aware 回调invokeAwareMethods调用BeanNameAwareBeanFactoryAware
初始化前applyBeanPostProcessorsBeforeInitialization@PostConstructApplicationContextAware在这里触发
初始化invokeInitMethodsInitializingBean.afterPropertiesSet(),然后是自定义init-method
初始化后applyBeanPostProcessorsAfterInitializationAOP 动态代理的后置处理一般发生在这里
使用容器缓存单例对象Bean 可以正常被注入了
销毁前@PreDestroy通过DestructionAwareBeanPostProcessor触发
销毁DisposableBean.destroy()/destroy-method释放资源

这张表看着简单,但每一个阶段展开都有很多细节。

2.2 从doCreateBean看一遍完整主链

我建议你带着一个简单的UserService类,断点打在AbstractAutowireCapableBeanFactory#doCreateBean方法上,然后一路 Step Into。这是理解整个生命周期的最快路径。

doCreateBean的流程大致如下:

  1. createBeanInstance:根据构造器决定创建对象。如果是无参构造器就直接实例化,如果只有一个有参构造器 Spring 也会自动推断并寻找对应依赖。
  2. populateBean:这一步会把 BeanWrapper 里的属性填上值。这里最容易踩坑:如果 Bean A 依赖 Bean B,而 B 还没有创建,Spring 会在这里递归去创建 B,从而可能触发循环依赖处理(后面第 3 部分细讲)。
  3. initializeBean:这是整个初始化阶段的核心,内部大致是:
    • invokeAwareMethods:只处理三个 Aware 接口,BeanNameAwareBeanClassLoaderAwareBeanFactoryAware
    • applyBeanPostProcessorsBeforeInitialization:遍历所有注册的BeanPostProcessor,调用它们的postProcessBeforeInitialization
    • invokeInitMethods:先检查是否实现了InitializingBean,实现则调用afterPropertiesSet();再通过反射调用 XML 或注解声明的init-method
    • applyBeanPostProcessorsAfterInitialization:再次遍历所有BeanPostProcessor,调用postProcessAfterInitialization

一个容易搞混的点是ApplicationContextAware在哪里被调用。它并不是在invokeAwareMethods里执行的,而是由 Spring 内置的一个BeanPostProcessor——ApplicationContextAwareProcessorpostProcessBeforeInitialization阶段处理的。所以实际顺序通常是:BeanNameAwareBeanFactoryAware先触发,然后是ApplicationContextAware,再然后才是@PostConstruct

2.3 初始化方法之间的优先级到底怎么排

很多同学在同一个 Bean 里同时定义了@PostConstruct、实现InitializingBean、还配置了init-method,想知道到底哪个先跑。这个问题的答案并不是"只跑一个",而是会全部执行,且顺序固定:

  1. @PostConstruct注解方法
  2. InitializingBean#afterPropertiesSet()
  3. 自定义的init-method

为什么会是这个顺序?核心原因在于源码结构:@PostConstruct是通过CommonAnnotationBeanPostProcessorpostProcessBeforeInitialization来执行的,而afterPropertiesSet()init-method是在invokeInitMethods里执行的。既然在初始化之前有一道后置处理器的关卡,那么@PostConstruct自然排在前面。

如果你写了一个很极端的 Bean,同时配了三种初始化方式,执行顺序就是postConstructafterPropertiesSetinitMethod。实际开发中我不建议这么干,会让代码的初始化入口变得很分散,排查问题时容易跳来跳去。

销毁侧也有类似的优先级顺序:@PreDestroyDisposableBean#destroy()destroy-method。需要特别提醒的是:这些销毁回调只对单例 Bean 生效。原型作用域的 Bean,Spring 把对象交给你之后就不再追踪它的生命周期了,不会帮你调用销毁方法。如果你的原型 Bean 持有了数据库连接、线程池等需要释放的资源,得自己在业务代码里负责清理。

2.4 生命周期扩展点怎么选:别再什么都往@PostConstruct里堆

理解了生命周期里有哪些扩展点之后,下一个实际问题是:我写的通用逻辑应该放在哪一层?

  • 如果你要对容器里所有 Bean 做统一增强,比如给带有某个注解的 Bean 生成代理,那就实现BeanPostProcessor,在postProcessAfterInitialization里加工实例。
  • 如果你只对某个具体 Bean 做初始化准备,比如启动后预加载数据、校验配置项,用@PostConstruct很合适。
  • 如果你需要修改 BeanDefinition,比如动态改变 Bean 的作用域或属性,应该实现BeanFactoryPostProcessor
  • 如果你需要在所有单例创建完成后再做一次全量通知,可以考虑SmartInitializingSingleton

这里有一个实战经验:很多人喜欢在构造器里做重活,比如读配置、请求远程接口,这是非常危险的。对象构造时 Spring 的属性填充还没发生,依赖的其他 Bean 可能还没准备好,@Value的值也可能还是 null。把所有初始化逻辑放到@PostConstructafterPropertiesSet里,比放在构造器里要安全得多。

3. 三级缓存与循环依赖:Spring允许"先上车后补票"的底层设计

3.1 先模拟一个循环依赖现场

循环依赖的场景很常见:Bean A 依赖 Bean B,Bean B 又依赖 Bean A。如果是两人用构造器互相注入,Spring 基本无能为力,会直接报BeanCurrentlyInCreationException;但如果用的是 Setter 注入或字段注入,Spring 默认是能兜住的。

为什么构造器循环依赖解决不了?因为 Spring 创建 Bean 时,第一步就是调用构造器完成实例化。A 的构造器需要 B,Spring 就要先创建 B;B 的构造器又需要 A,Spring 再去创建 A;两个 Bean 都还卡在第一步,没有一个能提前暴露自己。而 Setter/字段注入就没有这个问题:A 实例化完成后,即便属性还没填,它已经是一个"真实存在"的 Java 对象了,可以先把这个不完整的对象暴露出去给 B 用,等 B 创建完再回头完成 A 的后续步骤。

这个过程很像"先上车后补票":人还没到终点,但可以先拿临时凭证上车,到站了再补全手续。

3.2 三个缓存各自存什么:成品、半成品、工厂

Spring 处理循环依赖的经典手段就是三级缓存。这三个缓存都定义在DefaultSingletonBeanRegistry里,是一组很朴素的 ConcurrentHashMap:

  • singletonObjects:一级缓存,存放完全创建好的单例 Bean 成品。
  • earlySingletonObjects:二级缓存,存放提前暴露的半成品 Bean,也就是已经实例化但属性可能还没填充完的对象。
  • singletonFactories:三级缓存,存放ObjectFactory,通过它可以在需要时生成"早期引用"对象。

正常流程下,getBean(A)会先查一级缓存,查不到就准备创建。创建过程中,doCreateBean在实例化完成、属性填充之前,会先调用addSingletonFactory,把 A 对应的ObjectFactory放进三级缓存。这个时机非常重要:必须是在 A 对象已经 new 出来之后、开始填充属性之前,因为只有这样才能让依赖 A 的其他 Bean 拿到 A 的引用。

整个循环依赖的执行过程我可以描述成下面这样:

  1. A 开始创建,实例化完成,向三级缓存放入 A 的ObjectFactory
  2. A 执行populateBean,发现需要注入 B。
  3. 容器去创建 B,B 实例化完成,也向三级缓存放入 B 的ObjectFactory
  4. B 执行populateBean,发现需要注入 A。
  5. 容器查 A:一级缓存没有,二级缓存没有,从三级缓存拿到 A 的ObjectFactory,调用后生成 A 的早期引用,放入二级缓存。
  6. B 拿到了 A 的早期引用,B 继续完成属性填充、初始化,最后放入一级缓存。
  7. A 的populateBean继续执行,注入 B。
  8. A 继续完成初始化,最后放入一级缓存。

3.3 为什么必须要有第三级缓存?两级不够吗?

这是 Spring 面试里最多人问的问题。网上很多答案只说"三级缓存是为了解决 AOP 代理与原始对象的统一",这个方向是对的,但不够具体。

假如只有一、二级缓存:A 实例化完成后直接以一个原始对象放进二级缓存,B 拿到的就是 A 的原始对象。如果 A 需要被 AOP 代理,而代理的生成一般发生在 Bean 初始化之后的postProcessAfterInitialization阶段,那么等 A 正常走完生命周期,容器一级缓存里存放的是代理对象,B 手里却还拿着早期那个原始对象。这样一来,B 调用 A 方法时不会被增强逻辑拦截,事务、权限等切面就失效了。

三级缓存为什么能弥补?因为三级缓存放的不是对象,而是ObjectFactory。当 B 真的需要提前引用 A 时,Spring 会调用这个工厂的getEarlyBeanReference,这个过程会提前触发SmartInstantiationAwareBeanPostProcessor的处理逻辑。如果 A 需要被 AOP 代理,代理对象会在这一步就被提前创建出来,之后 A 的postProcessAfterInitialization阶段会识别到"已经提前代理过",不会重复生成第二个代理,最终保证一级缓存和 B 手里拿到的引用是同一个对象。

换句话说:第三级缓存的价值不在于多存了一个"对象引用",而在于把"是否生成代理"的决策延迟到了真正发生循环依赖的那一刻。如果没有循环依赖,根本不会有人调用那个工厂,A 就按正常路径在初始化之后才生成代理,这样能避免无谓的提前代理开销;一旦真的发生提前引用,又可以通过工厂让代理逻辑提前介入,保证引用一致性。这种"按需触发"的能力,是二级缓存直接存原始对象做不到的。

3.4 不是所有循环依赖都能解,顺带回答一道高频追问

三级缓存不是万能的,下面这些情况即使开了三级缓存也解决不了:

  • 构造器注入的循环依赖。
  • Bean 的作用域是 prototype 的循环依赖。
  • 开启了allowCircularReferences=false,也就是显式禁止循环引用。
  • 部分@AsyncBean 或多线程代理场景下,早期引用与最终代理对象不一致导致的异常。

另外要注意:Spring Boot 2.6.0 开始,默认把spring.main.allow-circular-references置为了 false。也就是说如果你在较新版本的项目里直接写循环依赖,启动阶段就会报错。很多人觉得这是 Spring Boot "搞事情",其实官方意图就是逼开发者尽量不要用循环依赖,因为一旦出现 A 依赖 B、B 依赖 A 这种结构,代码的初始化顺序就会变得很隐晦,排查问题成本很高。

我在实际项目里的建议是:如果是老项目、历史代码已经写死了循环依赖,临时用@Lazy或配置属性放开是可以理解的;但新写的代码,优先通过拆分 Service、引入中间层、或者在必要时使用ObjectProvider延迟获取来打破环。理解容器能处理循环依赖,和主动制造循环依赖,完全是两码事。

4. 执行流程的完整拼图:从refresh到一次请求的来龙去脉

4.1 Spring Boot 启动:从启动类到 refresh 的入口

现在绝大多数项目已经基于 Spring Boot 开发,那么"执行流程"首先要回答的问题是:启动类里的SpringApplication.run()到底是怎么把 Spring 容器拉起来的?

SpringApplication.run内部经过环境准备、SpringApplicationRunListeners 广播之后,最终会进入一个关键方法:refreshContext(context)。这个方法会调用AbstractApplicationContext#refresh()

不管你是用AnnotationConfigApplicationContextClassPathXmlApplicationContext,还是 Spring Boot 里的ServletWebServerApplicationContext,核心启动逻辑最终都收敛到refresh()。只不过 Spring Boot 的ServletWebServerApplicationContext会在某个扩展点额外完成内嵌 Web 服务器的创建和启动。

Spring Boot 能实现"自动配置",核心功臣是@EnableAutoConfiguration。它通过AutoConfigurationImportSelector读取META-INF/spring/...下的自动配置文件,把一堆条件注解(@ConditionalOnClass@ConditionalOnMissingBean)修饰的配置类导入容器。这些配置类最终会被解析成 BeanDefinition,然后进入我们前面讲的 Bean 创建流程。

4.2 refresh()里的关键步骤:每个BeanManager都该懂的启动顺序

AbstractApplicationContext#refresh()是整个 Spring 容器生命周期的心脏,十几个步骤环环相扣。没必要全背,但下面几条主线我建议你把它们刻在脑子里:

prepareRefresh():准备工作,设置容器启动时间、活跃状态,初始化PropertySources,遇到initPropertySources抛出的异常会在这里失败。

obtainFreshBeanFactory():如果是可刷新的容器实现,这一步会销毁旧的 BeanFactory 并创建新的DefaultListableBeanFactory,然后加载 BeanDefinition。这也是每次refresh后 BeanDefinition 重新被读取的根因。

prepareBeanFactory():配置 BeanFactory 的标准特性,比如设置类加载器、注册ApplicationContextAwareProcessor、忽略一部分不需要自动注入的接口、注册EnvironmentMessageSource等单例。

invokeBeanFactoryPostProcessors():执行BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor。这里有一个非常关键的类——ConfigurationClassPostProcessor,它会解析@Configuration类、@ComponentScan扫描结果,把大量业务 Bean 注册成 BeanDefinition。此时 Bean 对象还没创建,但"名单"已经定好了。

registerBeanPostProcessors():把所有实现了BeanPostProcessor的类注册到容器里,但注意,这里只是注册,不会立刻执行它们的方法,真正的执行要等到单个 Bean 创建到对应阶段时才会触发。

后面还有initMessageSourceinitApplicationEventMulticasteronRefreshregisterListeners等步骤,最后两步很重要:

finishBeanFactoryInitialization()实例化所有剩下非懒加载的单例 Bean。前面讲的所有生命周期细节,包括三级缓存、BeanPostProcessor 回调,都在这个阶段集中爆发。

finishRefresh():初始化生命周期处理器,发布ContextRefreshedEvent事件,清理早期事件等。

所以你会发现一个很重要的规律:BeanFactoryPostProcessor的执行时机会早于任何 Bean 实例化,因为它的目标是 BeanDefinition;而BeanPostProcessor却必须比业务 Bean 先完成注册,否则它没法拦截后续 Bean 的创建过程。很多自定义扩展失效,往往就是因为没搞清楚自己应该实现前一个还是后一个接口。

4.3 一次HTTP请求在Spring MVC里经历了什么

如果只是说容器内部的启动流程,那还没有覆盖标题里的"执行流程"。一个应用启动后,90% 的日常运行时间都在处理请求,所以必须把 Spring MVC 的请求链路也串一遍。

一个 HTTP 请求从浏览器发出后,先到达 Servlet 容器(Tomcat 等),然后经过 Filter 链。Filter 和 Spring 容器不是同一个体系,但可以通过DelegatingFilterProxyFilterRegistrationBean把 Filter 纳入 Spring 管理,@WebFilter+@Order就是常见的组合。

真正进入 Spring MVC 的入口是DispatcherServlet。这个 Servlet 的doDispatch方法是整个 Web 执行流程的调度中心,核心步骤是:

  1. HandlerMapping根据请求路径找到对应的HandlerExecutionChain,这个链上包含了处理器和一组HandlerInterceptor
  2. HandlerAdapter负责适配处理器并调用它。这里特意用 Adapter,是因为 Spring MVC 里 handler 不只可以是@Controller方法,还可以是HttpRequestHandlerServlet等不同类型,不同处理器需要不同的适配器。
  3. 执行拦截器的preHandle方法。
  4. 调用 Controller 方法。如果 Controller 参与了 AOP,比如方法上有@Transactional,真正调用的是一个经过 BeanPostProcessor 生成的代理对象。
  5. 如果 Controller 返回的是ModelAndView,会经ViewResolver解析渲染;如果是@ResponseBody/@RestController,则经HttpMessageConverter序列化为 JSON。
  6. 视图渲染完成后,执行拦截器的postHandleafterCompletion

理解这条链路对排查问题很有帮助。比如你自定义了一个拦截器,却发现它没有拦截某些静态资源,那大概率是静态资源映射没有走DispatcherServlet;比如你在 Controller 里打印日志,发现执行顺序和预期不一致,就需要去看拦截器preHandle的返回值、异常处理器的位置,而不是盲目怀疑 Spring 乱序。

4.4 把容器构建链和请求处理链画成一张"脑内地图"

到这里,我们可以把两条链路收拢起来:

启动时,refresh()负责创建容器,finishBeanFactoryInitialization()逐个实例化所有单例 Bean;DispatcherServlet或内嵌 Web 服务器通过onRefresh等步骤启动;容器发布ContextRefreshedEvent事件,整个应用对外就绪。

请求时,请求先经过 Filter,再进入DispatcherServletDispatcherServlet通过HandlerMapping定位处理器,通过HandlerAdapter调用 Controller;Controller 里注入的 Service 是容器在启动时通过生命周期创建并完成 AOP 代理的 Bean;事务、缓存、安全等切面逻辑,都发生在对这些 Bean 的调用过程中,而不是额外的一套魔法。

换句话说,Bean 生命周期是"对象构建链",执行流程是"请求调用链",两者在 Controller 这个节点交汇:Controller 本身在启动期经过了完整生命周期,它的方法在运行时又被代理对象包了一层。理解这个交汇点,就理解了 Spring 整个框架的骨架。

5. 这些原理到底怎么用:面试答题、源码阅读与实际选型的一点经验

5.1 高频面试题背后的考点是什么

既然热词里频繁出现"Spring 面试题、Bean 的生命周期、Spring IoC、Spring 三级缓存原理",我索性把几个常见问题绑在一起分析一下。

面试官问"Bean 的生命周期是什么"时,真正想听的不是四个阶段,而是你有没有形成阶段+扩展点的结构认知。我建议你在面试时这样拆:

  • 先说大节奏:实例化、属性填充、初始化、销毁。
  • 再说初始化阶段细化:Aware 回调、BeanPostProcessor 前置处理、InitializingBean、init-method、BeanPostProcessor 后置处理。
  • 补充两个亮点:ApplicationContextAware是通过ApplicationContextAwareProcessor触发的,@PostConstructpostProcessBeforeInitialization阶段执行,所以它排在afterPropertiesSet之前。
  • 最后加一句销毁侧规则:单例才有销毁回调,原型 Bean 容器不负责销毁。

这一套答下来,面试官基本能确认你不是背答案。

问"Spring 怎么解决循环依赖"时,建议回答顺序是:场景是什么、三个缓存分别存什么、什么时候写入/获取、为什么二级不够三级来凑、哪些场景解不了。重点是讲清楚"三级缓存里放的是 ObjectFactory 而不是对象",因为这是设计精髓。

问"Spring Boot 启动流程"时,只要你能抓住refresh()的主干,再补充@EnableAutoConfiguration和自动配置导入机制,基本就覆盖了大半。你不需要背每个方法的源码行号,但得知道BeanFactoryPostProcessor先于 Bean 实例化,BeanPostProcessor在 Bean 创建时被回调这两个关键节点。

5.2 我自己读源码的方法:不要在 start 类上迷路

很多人看 Spring 源码,第一反应是找个源码分析专栏从头看,但往往看着看着就丢了。我自己的经验是从自己的项目起步,打断点去"逆着调用栈读"。

你只需要随便建一个AnnotationConfigApplicationContext,加载一个最简单的配置类,然后在 Spring 的refresh()方法里打一个断点,跑一次。顺着调用栈你会看到类似这样的主线:

AnnotationConfigApplicationContext#refreshfinishBeanFactoryInitializationpreInstantiateSingletonsgetBeandoGetBeancreateBeandoCreateBean

然后你再在doCreateBean里面单步走一遍,就能亲眼看到createBeanInstance之后是populateBean,然后是initializeBean。我敢说,这种自己追踪一次的印象,比读十篇源码分析都深。

读源码的时候有一个技巧:把方法名当"路标",先不管每一行具体逻辑,只关注主干在调用哪个方法、什么时候注册了什么后置处理器。等主干通了,再深入细节。碰到类似的标记型接口,比如AwareInitializingBeanDisposableBean,你要留意它们通常对应着一个专用回调阶段,Spring 的很多设计都是"接口即契约、后处理器即执行点"。

5.3 把知识落在自己的组件上才是终点

如果你不做 Spring 二次开发,也不面试,这些原理是不是就没用了?我不这么认为。一旦你需要自己封装公司内部的基础组件,比如多租户切换、幂等控制、日志脱敏、动态数据源,你一定会遇到一个问题:在 Spring 的哪个环节切入最合适。

我的经验是:

  • 要动态注册额外的 BeanDefinition,用ImportBeanDefinitionRegistrarBeanDefinitionRegistryPostProcessor
  • 要给某个注解的 Bean 生成代理,用BeanPostProcessor结合 JDK/CGLIB 动态代理。
  • 要在所有 Bean 初始化完成后执行通知,比如打印启动信息、检查必需配置,用SmartInitializingSingleton或监听ApplicationReadyEvent
  • 要感知 Bean 从容器移除,实现DisposableBean或注册DisposableBeanAdapter逻辑。

以上每一种场景,都要求你对生命周期和执行流程有足够清晰的理解。这也是为什么我一直建议:不要等到面试前才翻这些概念,平时遇到报错、遇到 Bean 初始化顺序问题,就去源码里找一次答案。Spring 其实并不神秘,它只是把"什么时候创建、什么时候注入、什么时候回调、什么时候销毁"这几件事设计得很严谨。你把这几件事的时间线理清了,后面再学 Spring Boot 的自动配置、Spring Cloud 的启动装配,都会觉得顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询