我先说个结论:Spring的refresh()方法,是所有Spring面试题的最大公约数。不管是问IoC原理、Bean生命周期、三级缓存、@Autowired怎么生效,还是问你项目启动时到底发生了什么,追到最后都会落到AbstractApplicationContext.refresh()这一个方法上。方法名叫“刷新”,但干的是整个Spring容器的初始化——创建BeanFactory、加载BeanDefinition、执行后处理器、实例化所有单例Bean、发布事件,一条龙全在这里。我见过不少同事,包括我自己最开始读Spring源码的时候,都是从这个方法切入,然后在各种doXXX和registerXXX之间迷路,一上午过去脑袋里只剩一堆方法名。这篇文章就把我自己反复摸爬滚打出来的阅读技巧整理一遍,目标是让刚开始读源码的人也能顺着refresh()这条线,把Spring容器启动过程真正走通。
1. 为什么所有Spring面试题最终都会落在refresh()上
1.1 refresh()是IoC容器的“总导演”
先看一个最简单的启动方式:
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); }点进AnnotationConfigApplicationContext这个构造器,你会发现它做了两件事:先调用无参构造器初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner,然后调用register(componentClasses)把配置类注册成BeanDefinition,最后一行就是refresh()。
如果是老项目用的ClassPathXmlApplicationContext,构造器最后也是refresh()。到了Spring Boot,SpringApplication.run()内部创建好ApplicationContext之后,进入的仍然是AbstractApplicationContext.refresh(),只是中间多了一层refreshContext()。也就是说,不管你是注解驱动、XML驱动还是Spring Boot,最终启动入口都是这一个方法。
所以读refresh(),不只是读一个方法,而是拿到了整个IoC容器的执行地图。你以后看到任何关于“Bean什么时候创建”“配置类什么时候解析”“AOP切面什么时候生效”的疑问,都可以在这张地图上定位到具体坐标。
1.2 阅读能力的三层分水岭
我自己把读refresh()的水平分成三层:
- 第一层:能背出方法里12个步骤的名字,知道每步大概干嘛。
- 第二层:能说清楚
invokeBeanFactoryPostProcessors和finishBeanFactoryInitialization这两个核心步骤内部的关键分支,尤其是后处理器的执行顺序。 - 第三层:能结合实际问题定位源码,比如循环依赖为什么三级缓存能解,为什么
@Autowired能识别ApplicationContextAware这类接口。
这篇文章的定位,是帮你从第一层扎实走到第二层,并且给第三层铺好路。我不打算把每行代码都抄出来讲,而是讲“怎么读”比“读到了什么”更重要的那些方法。
2. 读源码前的三件套:环境、断点、目标
2.1 源码怎么拉、怎么导入才不劝退
很多人读Spring源码第一步就卡住了:用Maven下载的sources.jar虽然能看代码,但注释残缺,跳转体验也差,方法多了以后根本理不清。我的建议是直接拉GitHub上的源码工程,自己导入IDEA。
git clone https://github.com/spring-projects/spring-framework.git cd spring-framework git checkout v5.3.39注意,spring-framework由Gradle管理,不是Maven。IDEA导入时选择Gradle项目,等待依赖下载完就行。JDK版本建议8或11,太新的JDK反而可能在编译工具链上出幺蛾子。
如果你平时用的是Spring Boot 2.7.x,对应Spring Framework 5.3.x;如果用Boot 3.x,那Framework是6.x。我建议第一次读选5.3.x,网上讨论最多、资料最全,坑也都被前人踩得差不多了。
提示:源码工程里有很多模块,你只需要关注
spring-context、spring-beans这两个模块的源码,其他如spring-web、spring-jdbc暂时不用管。IDEA里对着AbstractApplicationContext直接跳转,也可以。
2.2 第一个断点应该打在哪
环境准备好以后,不要急着到处点。建一个最普通的Java工程,引入你本地源码模块,然后写一个最小启动类:
@Configuration @ComponentScan("com.demo") public class AppConfig { }public class DemoApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.close(); } }在AbstractApplicationContext.refresh()方法的第一行打个断点,然后以Debug模式运行。停住之后,看一下IDEA左下角的调用栈:AnnotationConfigApplicationContext的构造器在调用refresh(),而底层到AbstractApplicationContext。这个栈帧就是你的“锚点”,接下来所有追踪都从这个锚点出发。
从断点开始,我建议你开启“Step Over”模式,一步一步看refresh()内部方法的调用顺序,先别用“Step Into”钻进去。这一步的目标是建立“顺序感”。等你把12个步骤的名字都混了个脸熟,再决定往哪个方法里钻。
2.3 每次阅读只带一个目标
读源码最忌讳的就是想一口气全看懂。我的习惯是每次打开refresh()之前,先给自己定一个小目标:
- 第一次:只看主流程,12个步骤分别干什么。
- 第二次:只跟
finishBeanFactoryInitialization这个单例创建链路。 - 第三次:只关注
invokeBeanFactoryPostProcessors的后处理器调度顺序。
一次一题,读不懂就停,改天再来。带着目标读,比漫无目的扫代码高效得多。
3. refresh()十二步:先背框架,再逐个击破
3.1 十二步对照表
先把refresh()的主体结构砸出来。Spring 5.3.x里,AbstractApplicationContext.refresh()大致的流程如下,我配上了一句话作用说明:
| 步骤 | 方法名 | 一句话作用 |
|---|---|---|
| 1 | prepareRefresh() | 记录启动时间、设置容器状态、初始化属性源、收集早期监听器 |
| 2 | obtainFreshBeanFactory() | 创建/刷新BeanFactory,并加载BeanDefinition(如XML场景) |
| 3 | prepareBeanFactory(beanFactory) | 为BeanFactory配置类加载器、SpEL解析器、Aware回调等 |
| 4 | postProcessBeanFactory(beanFactory) | 模板方法,留给子类扩展(比如Web容器相关) |
| 5 | invokeBeanFactoryPostProcessors(beanFactory) | 执行BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor |
| 6 | registerBeanPostProcessors(beanFactory) | 注册所有BeanPostProcessor,但此时不执行 |
| 7 | initMessageSource() | 初始化国际化消息源 |
| 8 | initApplicationEventMulticaster() | 初始化事件多播器 |
| 9 | onRefresh() | 模板方法,子类扩展(如Web服务器启动) |
| 10 | registerListeners() | 把监听器Bean注册到多播器,并广播早期事件 |
| 11 | finishBeanFactoryInitialization(beanFactory) | 实例化所有非懒加载单例Bean,核心步骤 |
| 12 | finishRefresh() | 初始化生命周期处理器,发布ContextRefreshedEvent |
后面还有catch (BeansException ex)和finally { resetCommonCaches(); }。异常分支干的是销毁已经创建的Bean,避免容器处于半初始化状态;finally清理的是反射缓存、ResolvableType缓存这些元数据。
这张表值得先记下来。不用死记,读两遍源码再看这张表,自然就记住了。
3.2 前三步:准备、拿工厂、做“出厂设置”
第1步prepareRefresh()相对简单,核心是几点:设置容器启动时间、把closed和active标志位翻转、初始化PropertySources占位符、准备一组earlyApplicationListeners。它有一个模板方法initPropertySources(),默认空实现,留给子类填充环境变量。阅读时你只需要知道“这一步是热身”,代码本身不值得逐行盯。
第2步obtainFreshBeanFactory()值得重点看一眼,因为它是“BeanFactory从无到有”的关键。AbstractApplicationContext里这个方法很短,是个模板方法骨架,具体实现取决于子类:
GenericApplicationContext的refreshBeanFactory()里会new一个DefaultListableBeanFactory,然后调用loadBeanDefinitions(beanFactory)。AbstractRefreshableApplicationContext还会设置allowBeanDefinitionOverriding、allowCircularReferences这些标志位。
这里有一个很反直觉的点:refresh()方法本身并没有“创建BeanFactory”的逻辑,真正创建是子类干的。所以读refresh()时,很多代码其实是“多态调用”,阅读时别把obtainFreshBeanFactory()当成一个简单getter,它是模板方法。
第3步prepareBeanFactory(beanFactory)是做“出厂设置”的。它给BeanFactory设置了类加载器、SpEL表达式解析器、占位符解析器,然后注册了一堆“手动指定依赖”的组件:
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this);同时还会ignoreDependencyInterface(EnvironmentAware.class)、ignoreDependencyInterface(ApplicationEventPublisherAware.class)等等。这个ignore的意思是:当Spring准备给Bean做属性注入时,遇到这些Aware接口,不要走自动装配,而是由容器在之后用回调方式显式设置。这就是为什么你的ApplicationContextAware接口能拿到容器引用,而不是靠@Autowired塞进来。
阅读技巧:第3步不需要把每一行注册都背下来,只需要明白它是在“调教”BeanFactory,让它具备Spring容器该有的各种基础设施能力。
3.3 第5、6步:两个“后处理器”的分水岭
invokeBeanFactoryPostProcessors和registerBeanPostProcessors是12步里最难啃的两块。它们名字很像,很多人读完就混了。我提供一个区分角度:
invokeBeanFactoryPostProcessors:立刻执行。这些后处理器专门用来修改BeanDefinition,比如ConfigurationClassPostProcessor就是在这里解析@Configuration、@ComponentScan、@Bean的,因此BeanDefinition真正的内容大多在这时候才补全。registerBeanPostProcessors:只注册、不执行。它把所有BeanPostProcessor实例放进BeanFactory的一个list里,等后面createBean时才会被逐个调用,用来干预Bean实例化过程。
记住一句话:一个管“定义”,一个管“实例”。这两个步骤的具体阅读方法,我放在第4章专门展开,这里先把位置和分工搞清楚。
3.4 第7到10步:消息、事件和Web服务器
第7步initMessageSource(),看一眼就行。它先从BeanFactory里找有没有用户自定义的MessageSource,没有就用DelegatingMessageSource兜底。
第8步initApplicationEventMulticaster()同理。优先用用户自定义的ApplicationEventMulticaster,没有就new一个SimpleApplicationEventMulticaster。注意这里有个细节:多播器会尝试从BeanFactory里拿TaskExecutor,如果有就把异步执行器塞进去,这样事件就能异步发布。自己业务里想要异步事件,回头可以往这个方向配。
第9步onRefresh()是模板方法,Web场景下子类会在这里启动内嵌Tomcat/Netty。纯注解容器里它是空实现。
第10步registerListeners(),把容器里实现了ApplicationListener接口的Bean注册到多播器。它还会把prepareRefresh()阶段收集的早期事件(earlyApplicationEvents)先缓存起来,等后续finishRefresh()发布ContextRefreshedEvent再一并分发。
阅读技巧:这四步不是核心矛盾,快速扫过即可。如果你本来就不关心事件机制,可以直接跳到第11步,后面前后文也不会有太大割裂感。
3.5 第11、12步:真正的主菜和收尾甜点
第11步finishBeanFactoryInitialization(beanFactory)是全文最重要的地方。它做两件事:
- 给BeanFactory设置类型转换器
ConversionService和字符串解析器EmbeddedValueResolver。 - 冻结Bean定义(
freezeConfiguration()),然后调用preInstantiateSingletons()实例化所有非懒加载的单例Bean。
preInstantiateSingletons()会遍历所有BeanDefinition,跳过抽象类、跳过懒加载和原型Bean,然后对每个单例Bean调用getBean(beanName)。也就是说,你业务里大部分@Service、@Component、@Bean单例对象,都是在这一步被真正new出来的。
第12步finishRefresh()是收尾:清理JVM一级缓存中的资源类、初始化LifecycleProcessor、调用onRefresh()(这一步是web容器启动逻辑的位置)、发布ContextRefreshedEvent。
提示:事件发布的顺序很有讲究。必须在所有单例Bean都创建完才发
ContextRefreshedEvent,否则监听器回调时想依赖其他Bean可能还没创建好。
4. 最值得死磕的三个子流程:别人不会告诉你的那些点
4.1 invokeBeanFactoryPostProcessors:先看调度顺序,再看内部实现
这是refresh()里最劝退的一个方法,因为点进去以后你会看到大量循环和递归。我第一次读时一路Step Into,直接进了ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry,然后又跳进一个内部类的几百行代码里,人懵了。
后来我换了策略:第一遍只看“入口调度顺序”,绝不深入任何具体PostProcessor的实现。
入口调度顺序大致是这样的:
- 先收集所有
BeanDefinitionRegistryPostProcessor类型的Bean。 - 区分
PriorityOrdered、Ordered、普通无序三类,按顺序依次执行它们的postProcessBeanDefinitionRegistry()方法。 - 执行完后还会再次从BeanFactory里扫描,因为前一轮处理可能注册了新的
BeanDefinitionRegistryPostProcessor,若有则继续循环执行。 - 全部
BeanDefinitionRegistryPostProcessor执行完毕,最后才统一执行普通BeanFactoryPostProcessor的postProcessBeanFactory()。
这里为什么容易晕?因为postProcessBeanDefinitionRegistry()执行过程中,往往又会产生新的后处理器,Spring需要反复重新扫描,所以代码里才有while循环和repeat标志。你如果跟着某一轮递归进去看细节,就很容易丢掉“当前处于第几轮”这个上下文。
阅读技巧就是:先画一张“调度顺序图”,把PriorityOrdered → Ordered → 普通这个套路记下来,然后随便找一个具体的PostProcessor实现去验证,比如ConfigurationClassPostProcessor。等你验证完一个,这条“调度顺序”就算彻底吃透了。
4.2 registerBeanPostProcessors:顺序就是生命
这个方法比上一个温和,但同样有排序逻辑。它的执行顺序是:
- 注册实现
PriorityOrdered接口的BeanPostProcessor。 - 注册实现
Ordered接口的。 - 注册普通无序的。
- 注册
MergedBeanDefinitionPostProcessor(用于处理@Value、@Autowired、@PostConstruct等注解的元数据合并)。 - 最后注册一个
ApplicationListenerDetector,用来把实现了ApplicationListener接口的Bean自动登记成监听器。
你可能发现规律了:Spring在处理多个同类型组件时,特别喜欢用“PriorityOrdered → Ordered → 无排序”的套路。这个套路在invokeBeanFactoryPostProcessors里出现过,在registerBeanPostProcessors里又出现,后面你读MBeanExporter之类组件时还会见到。
阅读时我建议你找几个具体的后处理器看看它们的order值:
AutowiredAnnotationBeanPostProcessor:负责@Autowired和@Value注入,排序优先级很高。CommonAnnotationBeanPostProcessor:负责@Resource、@PostConstruct、@PreDestroy。ApplicationListenerDetector:排在最后。
为什么顺序重要?因为BeanPostProcessor在Bean创建过程中是链式调用的,前面的后处理器先把Bean加工一波,后面才能继续加工。顺序错了,注入时机就乱了。你只要理清“谁先谁后”,很多Bean初始化诡异问题都能定位到“某个后处理器没生效”上面。
4.3 finishBeanFactoryInitialization:三级缓存到底在哪一刻生效
死磕完两个后处理器,主菜就是finishBeanFactoryInitialization。这个方法的链路是:
preInstantiateSingletons() -> getBean(beanName) -> doGetBean(beanName) -> getSingleton(beanName) // 查三级缓存 -> singletonFactory.getObject() // 缓存都没有,才走createBean -> createBean(beanName) -> doCreateBean(beanName)很多人读到这里又迷糊了,因为doGetBean非常长。我的建议是:这条链路里,你先只盯DefaultSingletonBeanRegistry这个类,把三级缓存的读写时机搞清楚,再往doCreateBean里钻。
三个缓存的字段定义在DefaultSingletonBeanRegistry:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);singletonObjects:一级缓存,存放已经完整创建好的单例Bean。earlySingletonObjects:二级缓存,存放“提前暴露”的半成品Bean,这些Bean已经实例化但尚未完成属性填充/初始化。singletonFactories:三级缓存,存放的是ObjectFactory,调用它的getObject()可以拿到早期引用。
阅读时在getSingleton(String beanName, boolean allowEarlyReference)上打条件断点,条件可以写beanName.equals("userService")。你会看到以下场景:
- 第一次
getSingleton调用,三级缓存全都没命中,返回null。 - 进入
doCreateBean,实例化对象后,会调用addSingletonFactory(beanName, () -> getEarlyBeanReference(...)),这个lambda被放进三级缓存。此时Bean是个“半成品”。 - populateBean填充属性时,如果发现要注入的对象还没创建完,就会触发其他Bean的创建,形成循环。循环发生时,其他Bean能通过三级缓存的
ObjectFactory拿到这个半成品的早期引用,于是塞进二级缓存。 - 最终当前Bean完成初始化,调用
addSingleton(beanName, singletonObject)放进一级缓存,同时清掉二三级缓存。
这个机制困扰我很久的是“为什么需要三级缓存,而不是直接二级”。关键就在三级缓存存的是ObjectFactory,不是实例本身。由于ObjectFactory.getObject()可以返回经过AOP代理包装后的早期对象,Spring才能保证循环依赖发生时,注入的是代理而非裸对象。你顺着这个思路去读getEarlyBeanReference和AbstractAutoProxyCreator的getEarlyBeanReference重写,就能把AOP预演和循环依赖串起来。
阅读技巧总结:在doCreateBean里只关注三处——实例化后addSingletonFactory之前的代码、populateBean入口、addSingleton之前的状态。其他地方(比如各种applyMergedBeanDefinitionPostProcessors)这轮可以先跳过。
5. 踩坑实录:读源码时最容易掉的三个坑
5.1 钻进递归黑洞,忘了自己在哪
invokeBeanFactoryPostProcessors内部实现invokeBeanDefinitionRegistryPostProcessors时,会循环调用,还会嵌套处理dependsOn。很多人在这一步点着点着就彻底忘了自己是从哪进来的。
我的对策很简单:每次进入一个方法前,先问自己三句话——谁在调用我?我处于第几轮循环?处理的是哪个Bean?
IDEA的Debugger里,线程栈的“栈顶”通常是你正在执行的方法,“栈底”是线程的起点。你需要关注的是从refresh()到你当前位置之间的这几帧,不要管栈更深处Spring内部的一些机械操作。如果发现当前帧离refresh()已经隔了七八层,大概率你钻得太深了,先回到refresh()这一帧,重新按Step Over走一遍。
5.2 只读正常分支,忽略异常分支
读源码有个天然的坏习惯:跟着快乐路径走,看到if (xxx) return就跳过。但doGetBean里大量逻辑恰恰藏在条件分支里。比如:
if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { return createBean(beanName, mbd, args); }); }如果之前缓存里有,这个方法直接就返回了。可“没有缓存”的情况才更关键。我建议你阅读时看到if,先想想“如果条件不满足,下一行是什么”,强迫自己至少把分支的另一侧扫一眼。比如allowCircularReferences为false时,循环依赖会直接抛BeanCurrentlyInCreationException,这个分支你不看,后面排查“循环依赖为什么不生效”就会一头雾水。
5.3 版本漂移:网上文章对不上号
Spring Framework迭代很快。5.3.x和6.x之间,不仅包名从javax.换成jakarta.,内部代码结构也有变化。比如6.x里删掉了一些过时的*Aware接口注册逻辑,DefaultSingletonBeanRegistry的三级缓存实现虽然大体保留,但细节已经调整。
如果你照着网上2020年的文章去读6.x源码,很可能找不到对应类名或方法位置。记住一件事:读源码前先确认版本,最好以你项目实际依赖的Spring版本为准。你项目用的是Boot 2.7,就切到Framework 5.3.x;项目用Boot 3.x,就切到6.x。旧文章当思路参考,不要当代码字典。
6. 读完之后,refresh()给你留下什么
把refresh()读完以后,你再回头看那些面试题,会有一种“题都变透明了”的感觉:
- 控制反转发生在哪?
obtainFreshBeanFactory创建工厂,invokeBeanFactoryPostProcessors解析BeanDefinition,finishBeanFactoryInitialization实例化Bean——整个过程都是容器在替我们管理对象。 - 三级缓存什么时候写入?不是一开始写,而是对象实例化后、属性填充前,通过
addSingletonFactory写入。 @Autowired什么时候生效?在createBean的populateBean阶段,由AutowiredAnnotationBeanPostProcessor等后处理器完成。- 手写Spring应该参考什么?无非是:一个BeanDefinition注册表、一个单例池、一个后处理器列表、一个事件多播器,然后按
refresh()的顺序把它们串起来。
如果看完脑子里还是乱,我建议你分三次再读一遍:
第一次只跟主流程,把12个步骤名字背下来; 第二次跟finishBeanFactoryInitialization,重点看getSingleton和addSingleton; 第三次跟invokeBeanFactoryPostProcessors,只看调度顺序,不看内部实现。
每次阅读给自己设定一个明确的小问题,比如“BeanFactory是哪里new出来的”“BeanPostProcessor是在哪一步被注册的”“循环依赖的早期引用从哪里拿到的”。读完之后合上源码,用自己的话把流程讲一遍,卡住的点就是下次要回去重新看的地方。
我自己到现在每次遇到容器启动相关的诡异问题,仍然会先回到refresh()这张地图上定位坐标,而不是漫无目的地全局搜索。这个方法的可靠性,远比记住几行源码高得多。希望这篇阅读技巧,也能让你下次打开Spring源码时不再发怵。