1. 单例bean实例化在整个启动流程里的位置
先看结论:SpringBoot启动时执行的核心方法是SpringApplication.run(),一路调用到AbstractApplicationContext.refresh()。在refresh()里有个finishBeanFactoryInitialization(beanFactory)方法,通俗说就是“初始化所有非懒加载的单例bean”。这一步做完,容器里该有的对象基本都准备好了。这次要讲的“单例bean实例化”,就是在finishBeanFactoryInitialization往下走的过程中发生的。
如果对源码还不太熟,可以先记一张粗粒度路线图:
SpringApplication.run() -> AbstractApplicationContext.refresh() -> finishBeanFactoryInitialization(beanFactory) -> preInstantiateSingletons() -> getBean(beanName) -> doGetBean(beanName, ...) -> getSingleton(beanName) // 先从三级缓存拿 -> createBean(beanName, ...) // 没拿到就创建 -> doCreateBean(beanName, ...) // 真正实例化 -> createBeanInstance(...) // 构造器/工厂方法实例化 -> populateBean(...) // 属性填充 -> initializeBean(...) // 初始化方法、AOP等单例bean实例化的核心代码集中在createBeanInstance这一步,也就是“对象到底是怎么 new 出来的”。但整个流程里还有一堆前置逻辑,比如getSingleton、beforeSingletonCreation、addSingletonFactory这些,它们决定了一个bean什么时候走“创建”分支,什么时候直接走“缓存获取”分支。把这些串起来看,才算真正理解单例bean实例化。
这篇文章适合刚把SpringBoot启动流程源码读了一遍,但对bean创建细节还有点模糊的同学。如果你已经能熟练说出BeanFactory、DefaultListableBeanFactory、AbstractAutowireCapableBeanFactory这层关系,那下面的内容会非常顺。要是看到这些类名有点陌生,也可以先打开源码对着走,每个方法我都标了类名,跟着找就行。
2. getBean这条主链路怎么走的
2.1 doGetBean里的类型检查
入口是AbstractBeanFactory.getBean(String name)。它内部直接委托给doGetBean,这个do前缀方法在Spring源码里很常见,意思是真正干活的方法。
doGetBean一进来先做两件事:
final String beanName = transformedBeanName(name); Object beanInstance = getSingleton(beanName);第一行transformedBeanName处理的是别名和带&前缀的FactoryBean名称。第二行就是去三级缓存尝试获取实例。这里有个细节:如果name以&开头,取的是FactoryBean自身;普通名称取的是FactoryBean生产的对象。初学者看到getSingleton以为只是从Map里取数据,其实它内部有一段尚不完善但足够用的逻辑,先把单例的创建状态给标记上,再查缓存。
如果三级缓存里没有这个bean,接下来会做一系列前置准备:
if (mbd.isSingleon()) { sharedInstance = getSingleton(beanName, () -> { return createBean(beanName, mbd, args); }); }这段代码极其关键。它是“先查缓存,没有就创建”的模板方法。getSingleton(String, ObjectFactory)这个方法会维护singletonsCurrentlyInCreation集合,保证同一个bean在同一时间不会被并发创建两次,然后回调ObjectFactory.getObject()触发真正的创建逻辑。
很多初学者卡在这一步,觉得getSingleton怎么重载了好几个版本。梳理一下就不乱了:
getSingleton(String beanName):纯查缓存,不触发创建。getSingleton(String beanName, ObjectFactory<?> singletonFactory):带创建回调,查不到就调用工厂创建,创建后加入缓存。getSingleton(String beanName, boolean allowEarlyReference):循环依赖中提前暴露对象时用的,后面细说。
整个doGetBean就是以这三种获取方式为核心,加上类型检查、依赖检查、Scope处理组成的。单独看某一段可能觉得绕,但整体意图就是:能复用就复用,不能复用才创建。
2.2 getSingleton的缓存读取逻辑
前面说getSingleton(String beanName)是纯查缓存,但缓存不是查一个Map,而是三级缓存。三个Map定义在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);三级缓存这个概念网上一搜一大堆,这里只从代码层面讲getSingleton是怎么遍历它们的:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.singletonFactories.get(beanName); if (singletonObject != null) { singletonObject = ((ObjectFactory<?>) singletonObject).getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } return singletonObject; }看清楚这段代码的执行路径:先查一级缓存singletonObjects;没查到,判断当前bean是否正在创建中;如果是,再查二级缓存earlySingletonObjects;还没有,且允许提前引用,才加锁查三级缓存singletonFactories,然后执行工厂的getObject(),把产生的对象提升到二级缓存,同时从三级缓存移除。
这段逻辑写了十年了,看着繁琐,但目的只有一个:在保证单例的唯一性的前提下,允许构造器之外的循环依赖提前暴露对象。关于循环依赖这块,后面专门拉一节详细讲,因为它是单例bean实例化绕不开的核心机制。
2.3 createBean入口与实例化后处理器
走完getSingleton没拿到实例,代码进入AbstractAutowireCapableBeanFactory.createBean(String beanName, RootBeanDefinition mbd, Object[] args)。
createBean不只是创建对象,它至少兼顾三块内容:
- 根据
RootBeanDefinition的beanClass解析出实际要实例化的Class。 - 执行
InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation,给开发者一个在标准实例化流程之前返回代理对象或自定义对象的机会。 - 检查
mbd.getMethodOverrides(),如果有方法覆盖(lookup-method、replaced-method),走CglibSubclassingInstantiationStrategy生成子类。
在正式创建前有一段很值得注意的代码:
Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; }resolveBeforeInstantiation方法名里的“resolve”意思是“尝试解析出bean”。它内部执行所有InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation,如果任意一个返回了非null对象,就直接返回。最有名的是AOP的AbstractAutoProxyCreator,它在postProcessBeforeInstantiation会判断当前bean是否有合适的通知器,如果有,直接生成代理对象返回。
这段逻辑的存在,使得“AOP代理”可以在实例化前就介入。日常开发中我们感知不到这个阶段的存在,因为绝大多数bean会走标准实例化流程,但在读源码时知道有这道门,后面看AOP相关源码会轻松很多。
3. 构造器推断是怎么找到"唯一"构造器的
3.1 为什么实例化前要先推断构造器
createBeanInstance方法负责真正的实例化。在这个方法里,Spring先做的事是找“用哪个构造器”。很多初学者会觉得,直接用无参构造器反射new一个对象不就行了吗?但实际业务里大量bean通过构造器注入依赖,Spring必须解析出调用哪个构造器、每个参数的值怎么来。
createBeanInstance的整体流程:
if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } short resolved = mbd.getResolvedConstructorOrFactoryMethod(); if (resolved == CONSTRUCTOR && mbd.getConstructorArgumentValues().isEmpty()) { return autowireConstructor(...); } if (mbd.hasMethodOverrides()) { return instantiateWithMethodInjection(beanName, mbd, args); } Class<?> clazz = mbd.getBeanClass(); if (clazz.isInterface()) { ... } if (System.getSecurityManager() != null) { ... } Constructor<?>[] ctors = clazz.getDeclaredConstructors(); return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, args);注意代码顺序:先看有没有工厂方法,再看有没有解析过的构造器结论,最后才走构造器自动装配。也就是说,Spring做了一系列快速通道判断,避免每次创建bean都重新推断构造器。
mbd.getResolvedConstructorOrFactoryMethod()这个字段是“推断结果缓存”。第一次创建bean时,ConstructorResolver会把推断出的构造器写回RootBeanDefinition,后续同类bean直接从缓存拿结论,不必再解析。这种“解析一次、多次复用”的设计在Spring里很常见,初读源码时可以特意留意这些缓存字段,往往是理解性能优化思路的好入口。
3.2 autowireConstructor的参数匹配过程
autowireConstructor是整个构造器推断的核心,逻辑集中在ConstructorResolver类。要理解它,先记住一个结论:Spring在一个类有多个构造器时,会按照“参数个数最匹配”的原则挑选。具体过程大致分三步。
第一步,整理候选构造器。默认情况下,clazz.getDeclaredConstructors()返回所有构造器。如果有@Autowired标注的构造器,那个构造器优先级最高,直接作为唯一候选。
第二步,处理参数。如果调用方通过BeanDefinition设置了构造器参数值,Spring会尝试将这些参数值按类型和索引匹配到具体的构造器参数上。这里涉及到ConstructorArgumentValues的解析,每个参数值可能是一个常量,也可能是一个RuntimeBeanReference(指向另一个bean的引用)。没有显式配置参数值时,Spring会把构造器参数当作“需要自动装配的依赖”,从容器里按类型查找候选bean。
第三步,也是最容易绕晕的地方:getArgumentValues方法。它内部会判断构造器参数是否有对应类型匹配的bean,如果找不到,会用TypeConverter尝试类型转换(比如字符串转数字),还找不到就抛UnsatisfiedDependencyException。
一个典型场景是单构造器配合无参构造器同时存在:
@Component public class DemoService { private final OtherService other; @Autowired public DemoService(OtherService other) { this.other = other; } }这时Spring优先选择带@Autowired的构造器。如果没有@Autowired,但只有一个构造器,Spring也会自动选择唯一构造器,然后从容器里找OtherService类型的bean。如果这个类型有多个实例,且参数名能对上,Spring还能通过参数名缩小范围,比如构造器参数叫otherServiceA,容器里叫这个名字的bean会被优先选中。
如果构造器参数既没有@Autowired,也没有构造器参数值配置,Spring会自动选无参构造器。所以很多“循环依赖报错”的场景,往往是因为类里只有一个有参构造器且参数之间存在循环,Spring没法绕过,只能报错。
3.3 构造器选择与实例化策略的配合
构造器找到之后,真正的newInstance调用发生在SimpleInstantiationStrategy.instantiate里:
if (bd.getMethodOverrides().isEmpty()) { return BeanUtils.instantiateClass(constructorToUse, args); }BeanUtils.instantiateClass内部就是最朴素的constructor.newInstance(args),然后处理了访问权限(setAccessible(true))和异常包装。这一层朴实无华,没有魔法。
如果类里有方法覆盖(lookup-method、replaced-method),实例化策略会变成CglibSubclassingInstantiationStrategy,通过CGLIB动态生成子类来覆盖方法,这在一些旧版Spring项目和特殊框架场景中会用到。日常开发中这个分支基本不触发,但了解它存在有助于解释“为什么有些类实例化后类型变了”——CGLIB生成的子类已经不是原始Class。
4. 单例bean实例化的三种具体方式
4.1 Supplier方式与工厂方法方式
很多人不知道的是,Spring在AbstractAutowireCapableBeanFactory.createBeanInstance里,第一步并不是反射构造器,而是看beanFactory是否支持Supplier接口注入:
Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); }Supplier是函数式接口,使用方式是在注册bean时传入一个lambda表达式,Spring直接调用get()拿到对象。这个功能平时开发很少直接使用,但在SpringBoot自动配置和内部框架中,有些BeanDefinition通过Supplier定制创建逻辑,比反射构造器更灵活。
再看工厂方法方式。mbd.getFactoryMethodName() != null时,Spring调用instantiateUsingFactoryMethod。工厂方法有两种形式:
- 静态工厂方法:
FactoryClass.createInstance(),实例化逻辑在某个类的静态方法里。 - 实例工厂方法:
factoryBean.createInstance(),需要先创建工厂bean本身,再调用其方法。
工厂方法方式在生产代码里不多见,但在配置类中定义@Bean方法时,Spring内部也会走类似逻辑:@Bean方法编译后成为配置类的方法,创建bean时需要调用该方法。从源码角度说,@Bean方法生成的BeanDefinition自带factoryMethodName,名称就是方法名,工厂bean是配置类自身。
所以别小看instantiateUsingFactoryMethod,它在SpringBoot自动配置里出场率极高,几乎所有@Bean方法创建的bean都从这个入口走。
4.2 构造器反射方式的核心逻辑
没有Supplier、没有工厂方法时,Spring回到标准流程:解析构造器,反射实例化。
createBeanInstance后半段的代码是这样的:
Constructor<?>[] ctors = clazz.getDeclaredConstructors(); if (ctors.length > 0) { return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, args); } return instantiateBean(beanName, mbd);instantiateBean是最末位的方法,逻辑很简单:拿到无参构造器,执行instantiate。但注意前面的autowireConstructor不一定只选有参构造器,如果构造器参数解析结果为空,它也会选择无参构造器,区别在后者绕了一圈又回到了instantiateBean的逻辑。
这里把三种实例化方式的优劣明确讲一下:
| 实例化方式 | 触发条件 | 优势 | 劣势 |
|---|---|---|---|
| Supplier | 注册bean时设置了getInstanceSupplier | 完全自定义创建逻辑,灵活 | 使用少,普通开发接触不到 |
| 工厂方法 | BeanDefinition配置了factoryMethodName | 统一创建入口,便于集中控制 | 配置步骤多,出错时难排查 |
| 构造器反射 | 默认方式 | 简单直接,支持自动装配参数 | 循环依赖时无法提前暴露 |
三种方式的最终结果都一样:得到一个bean实例。但从“实例化时机”角度看,构造器反射方式支持在实例化前和实例化后插入各种后处理器,后两种方式会跳过部分后处理器逻辑,所以普通业务bean几乎都采用构造器反射方式。
4.3 实例化后马上做的事
createBeanInstance返回实例后,doCreateBean并没有立刻进行属性填充,中间还有一段很重要的操作:
if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); ... if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor dbp = (SmartInstantiationAwareBeanPostProcessor) bp; if (!dbp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continue; } } } }这里有一个BeanWrapper的存在,很多初学者不太理解为什么要包装一层。BeanWrapper接口继承自PropertyAccessor,提供属性访问、类型转换等功能。实例化后Spring立即把对象包装成BeanWrapper,后续的属性填充就是通过它完成的。
postProcessAfterInstantiation这个方法尤其值得注意。如果某个InstantiationAwareBeanPostProcessor返回false,Spring会跳过后续的属性填充阶段(populateBean里的自动装配)。这个机制比较隐蔽,一般框架不会随便返回false,但在某些场景下(比如对象已经手动赋值完毕,不需要容器填充),它是性能优化的重要手段。
如果从这层执行完再往下走,才是普通开发最熟悉的属性填充和初始化流程。但这次文章的主题是“实例化”,所以先到这里打住,把循环依赖单独拆开讲,因为它直接关系到实例化过程中对象如何提前暴露。
5. 三级缓存与循环依赖的深层机制
5.1 为什么需要三级缓存而不是两级
循环依赖,简单说就是A依赖B,B又依赖A。单例bean实例化过程中,如果A先创建,A的构造器完成实例化后进入属性填充,发现需要B;于是去创建B,B的属性填充又发现需要A;如果没有提前暴露机制,两边都等对方完成,就死锁了。
Spring针对“属性注入引发的循环依赖”给出的方案是三级缓存。要理解为什么必须三级,先要知道每一级缓存存的分别是什么:
- 一级缓存
singletonObjects:存最终成品bean,创建完成,属性填充完毕,初始化完毕。 - 二级缓存
earlySingletonObjects:存提前暴露的半成品bean,实例化完成但属性填充可能还没完成。 - 三级缓存
singletonFactories:存ObjectFactory<?>,每个factory能在需要时生产出“当前状态的bean实例”。
为什么不直接两级?因为Spring要支持“在创建过程中态地决定是否生成代理对象”。AOP代理有时需要在bean实例化之后、其他bean引用它之前就生成。三级缓存里存的是ObjectFactory,当B需要引用A时,A的ObjectFactory.getObject()才会返回一个可能被后处理器包装过的A。
如果直接用两级缓存,提前暴露的对象在二级缓存里就是固定值,后续AOP后处理器想加工也没机会。三级缓存的设计,本质上是把“真正实例化”和“是否代理”延迟到被引用那一刻才决定。这就是三级比两级多的核心原因。
5.2 addSingletonFactory的注册时机
三级缓存的ObjectFactory是什么时候放进去的?在doCreateBean创建完实例后,属性填充前:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }addSingletonFactory就是把lambda表达式放入singletonFactories:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.singletonObjects.put(beanName, singletonFactory); } } }注意这段代码的细节:它不光把工厂放进三级缓存,还往一级缓存塞了一个singletonFactory。但一级缓存那个Map的类型是Object,所以实际上它放进去的是一个伪装成实例的Factory对象,后续任何从一级缓存直接取这个对象的代码,都会得到一个ObjectFactory,不过这段逻辑被后续的getSingleton流程覆盖了,不会造成实际影响。这个写法在源码里属于特例,了解一下就行,不用深究。
getEarlyBeanReference是AbstractAutoProxyCreator会拦截的地方:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } return exposedObject; }getEarlyBeanReference是AOP代理能够介入循环依赖的关键突破口。如果当前bean需要被代理,AbstractAutoProxyCreator.getEarlyBeanReference会提前生成代理对象,并把这个代理对象放进二级缓存。后续另外的bean拿到的就是这个代理,而不是原始半成品。
5.3 构造器循环依赖为什么解决不了
明白了三级缓存的原理,自然能解释一个高频问题:为什么构造器注入的循环依赖连Spring也解决不了。
因为三级缓存的提前暴露动作发生在createBeanInstance之后,也就是对象必须已经被实例化出来,才有资格放进singletonFactories。但构造器循环依赖发生在createBeanInstance过程中:A要创建实例,必须先把B作为构造器参数传进去;Spring为了拿到B,必须先创建B;B又需要A作为构造器参数;此时A还没有完成createBeanInstance,三级缓存里根本没有A。
这就好比两个人都想先拿到对方的钥匙才能出门,结果互相等着拿钥匙,谁也没法先出门。
日常开发中遇到BeanCurrentlyInCreationException,可以重点排查:
- 是否使用了构造器注入形成了循环依赖。
- 是否开启了
allowCircularReferences=false。 - 是否有多个
@Autowired构造器导致Spring无法选择。
前两种基本是配置问题,后一种属于代码设计问题,建议把循环依赖的bean改为setter注入或@Lazy懒加载,让Spring绕过这个限制。不过从架构角度说,循环依赖本身是坏味道,能调整设计就调整设计,不要全指望框架兜底。
6. 常见问题与排查实战
6.1 BeanCurrentlyInCreationException现场还原
有一次我处理一个模拟项目报错,现场是这样的:一个定时任务类TaskSchedule构造器注入了ReportService,ReportService又注入了DataLoader,而DataLoader的构造器里反向依赖了TaskSchedule。启动时直接抛异常:
BeanCurrentlyInCreationException: Error creating bean with name 'taskSchedule': Requested bean is currently in creation: Is there an unresolvable circular reference?排查步骤不复杂:
第一步,看Spring启动日志里的bean创建顺序。异常信息里通常会打印 “Error creating bean with name 'xxx'”,这个名称往往是循环链的入口。可以先把涉及的类都列出来,画一个依赖图。
第二步,看异常抛出的位置。如果发生在autowireConstructor,基本就是构造器循环依赖。AbstractAutowireCapableBeanFactory的日志里会标明 “Bean with name 'taskSchedule' has been injected into other beans [...] in its raw version as part of a circular reference”,这个提示出现时说明循环依赖确实发生了,但Spring可能已经通过缓存解决了一部分。
第三步,确认注入方式。在引入这个问题的代码里,ReportService是用@Autowired构造器注入,DataLoader也是构造器注入。改成setter注入后,启动恢复正常。
这套排查方法对刚上手源码的同学很有价值:不是记住“构造器循环依赖无解”这个结论就够了,而是能通过日志和异常栈准确定位链条里的每一环,再选择最小的改动方案。
6.2getBean时类型转换失败
还有一个常见的坑,出现在doGetBean的最后一步:
if (requiredType != null && !requiredType.isInstance(bean)) { try { bean = getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { throw new BeanNotOfRequiredTypeException(...); } }这段代码意思是,调用getBean("someBean", TargetType.class)时,如果实际对象不是TargetType类型,Spring会尝试用TypeConverter转换,转不了就抛BeanNotOfRequiredTypeException。
这个报错经常发生在FactoryBean场景:你以为容器里存的是目标类型,实际上getBean("&beanName")获取的是FactoryBean自身,getBean("beanName")获取的是产品对象。如果你混用了&前缀,或者FactoryBean的getObjectType()返回类型不对,类型检查就会失败。
遇到类型转换失败,先检查两处:第一处是RootBeanDefinition的beanClass和resolveBeanClass的结果是否一致;第二处是FactoryBean的getObjectType()是否准确返回了产品类型。这两处没问题,大概率就不是类型转换自身的问题,而是你获取bean的方式不对。
6.3 单例bean实例化阶段可以插手的三个扩展点
读完源码,整理一下单例实例化过程中普通开发者可以干预的位置,按执行顺序排列:
BeanDefinitionRegistryPostProcessor:在所有bean实例化之前执行,可以动态修改BeanDefinition,包括替换beanClass、修改构造器参数、设置工厂方法等。InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation:在bean实例化前运行,可以直接返回一个替代对象,后面所有标准流程都跳过。SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference:循环依赖时提前暴露对象的最后加工点,AOP代理就是从这里产生的。
这三个位置分别对应“改定义”“截胡实例化”“调整提前暴露对象”,理解了它们,以后再碰到框架层面的bean创建定制,基本能猜到该从哪个接口下手。很多描述不清楚框架的源码里都有类似钩子,读Spring源码最大的收获之一就是学会识别这类扩展点。
7. 读源码时一定要避开的几个思维误区
读到这里,单例bean实例化主干已经走完。最后想说说方法论上的东西,这是我自己啃源码时最深的体会。
第一,别在一棵树上吊死。很多同学会揪着createBeanInstance从头一行一行读,结果被ConstructorResolver里几十个分支绕晕。正确的打开方式是多轮粗读:第一轮只需要知道doGetBean调用createBean,createBean调用doCreateBean,doCreateBean调用createBeanInstance;第二轮再去读createBeanInstance里的大分支;第三轮才深入ConstructorResolver。
第二,多跟实际报错对照。源码读得再多,不解决具体问题也记不牢。最实用的做法是遇到报错后在IDE里打断点,顺着调用栈走一遍。比如BeanCurrentlyInCreationException,断点打在getSingleton的isSingletonCurrentlyInCreation判断上,你会直观看到当前bean和正在创建的bean集合。
第三,不要试图背诵每个方法的完整代码。Spring源码浩如烟海,重点掌握主干、缓存、扩展点,细节用到时再回头看。这个系列的文章本身也是这个思路:每次讲一个主题,覆盖一个完整链路,不追求面面俱到。
单例bean实例化这部分,说实话,核心代码不算多,加起来也就几百行,但牵扯到的概念特别密集:三级缓存、构造器推断、工厂方法、后处理器,哪个单独拎出来都能展开一大篇。把这几个概念串成一条线——“缓存查不到就去创建,创建时先找构造器,构造器定好就反射实例化,实例化完立刻暴露工厂用于循环依赖,属性填充和初始化再逐步完成”——整条链路就非常清晰了。
下次再遇到bean创建相关的问题,建议你先在脑海里过一遍这条线,看问题卡在哪一环,再去翻对应源码。这个习惯养成了,Spring的题会越做越顺手。