一、开篇:面试官问 Bean 加载过程,到底在考察什么
在 Java 后端面试中,Spring 几乎是绕不开的一道坎。从应届生到高级工程师,从一面到终面,「讲讲 Bean 的加载过程」这道题出现的频率高得惊人。它看似是一道基础题,实际却能在二十分钟的追问中把候选人的知识储备、源码功底、工程经验层层剥开。
很多候选人一上来就背「实例化 → 属性填充 → 初始化」,背完之后被追问一句「那 BeanDefinition 是怎么来的」「三级缓存为什么要三级」就卡住了。原因在于,大多数人记住的是结论,而不是过程;记住的是名词,而不是背后的设计意图。
这篇文章不打算给你一份可以死记硬背的答案清单,而是带你从Spring 容器的启动流程出发,沿着源码调用链把「一个 Bean 从无到有」的完整过程走一遍。读完你会理解:
BeanDefinition 为什么存在,它是如何被扫描、注册、合并的;
Bean 的实例化、属性填充、初始化三个阶段各自做了什么;
BeanPostProcessor 这个「幕后灵魂」是如何贯穿全过程的;
循环依赖为什么能被解决,构造器注入的循环依赖为什么解决不了;
从
getBean到createBean的源码调用链长什么样。
本文基于 Spring 5.x 的源码讲解(Spring Boot 2.x 默认使用 Spring 5)。Spring 6 虽然在部分实现上有调整,但整体设计思想和核心流程一脉相承。掌握了这条主线,无论面试官换到什么版本、什么角度追问,你都能从容应对。
二、先建立全局认知:一个 Bean 到底要经历什么
在深入源码之前,我们先把「Bean 的加载过程」这句话拆开理解。很多人的误区在于,以为「加载」只是new一个对象这么简单。实际上,Spring 语境下的「加载」是一个包含注册、定义合并、实例化、注入、初始化等若干步骤的完整生命周期。
面试中常说的「Bean 的生命周期」,大致可以浓缩为下面这张图:
text
扫描配置类 ↓ 解析为 BeanDefinition ↓ 注册到 BeanDefinitionRegistry ↓ 合并 BeanDefinition(mergeBeanDefinition) ↓ 实例化前拦截(InstantiationAwareBeanPostProcessor) ↓ 实例化(构造器 / 工厂方法 / Supplier) ↓ 属性填充(依赖注入,含循环依赖处理) ↓ Aware 回调(BeanNameAware / BeanFactoryAware / ApplicationContextAware) ↓ BeanPostProcessor 前置处理 ↓ 初始化(@PostConstruct / InitializingBean / init-method) ↓ BeanPostProcessor 后置处理(生成代理,AOP) ↓ 放入单例池(一级缓存) ↓ 使用 ↓ 销毁(@PreDestroy / DisposableBean / destroy-method)
如果你能在面试开场就用这样一条主线把 Bean 的一生串起来,面试官的第一印象就已经稳了。接下来我们要做的,就是把这条主线的每一个节点拆开,讲清楚「谁调用了谁、为什么这么设计、哪些地方可以扩展」。
在拆解之前,有一组概念必须先搞清楚,否则后面的源码分析会像看天书:BeanDefinition、BeanFactory、ApplicationContext、BeanPostProcessor。下面先用一小节把前三个铺垫好,BeanPostProcessor 因为太重要,单独留到后面展开。
三、必备铺垫:BeanDefinition 与容器的关系
3.1 Bean 的定义不是对象本身,而是「图纸」
很多初学者会把 Java 对象和 Spring Bean 混为一谈,其实它们是两码事。Bean 是最终存放在容器中的对象,而 BeanDefinition 是描述这个 Bean 如何被创建出来的元数据,相当于一张「图纸」。
这张图纸上记录了哪些信息?我们看一下AbstractBeanDefinition的核心字段,大体可以归纳为五类:
类的信息:
beanClassName,决定要反射创建哪个类;作用域信息:
scope,默认是singleton,还有prototype、request、session等;依赖信息:
dependsOn、autowireMode、属性值集合PropertyValues;初始化与销毁:
initMethodName、destroyMethodName、lazyInit;一些开关:
primary、autowireCandidate、abstract等。
这一段信息在面试中特别值得说出来,因为它能证明你知道「Bean 的加载过程」其实包含两个层次:先是定义 Bean(注册 BeanDefinition),然后才是创建 Bean(实例化为对象)。前者是「图纸设计」,后者是「按图施工」。
3.2 BeanFactory 与 ApplicationContext:谁才是真正的容器
Spring 官方把容器抽象成了BeanFactory接口,它最核心的能力就一个:getBean。你可以把BeanFactory理解成最朴素的「Bean 仓库」,支持按名称、按类型取 Bean。
但在实际开发中,我们几乎不会直接使用BeanFactory,而是使用它的高级子接口ApplicationContext。二者有什么本质区别?
简单说:BeanFactory 是「懒汉」,ApplicationContext 是「饿汉」。
BeanFactory在getBean被调用时才真正创建 Bean;ApplicationContext在容器启动阶段就会把单例、非懒加载的 Bean 全部提前创建好,也就是我们常说的「启动时预实例化(pre-instantiate)」。这是它和BeanFactory最典型的差异之一。
这也引出了一个重要的面试点:为什么 Spring Boot 启动时创建了那么多单例 Bean 却感觉不到明显的性能问题?因为启动阶段本身就是一次性成本,提前创建可以保证运行期的稳定性,同时把循环依赖、配置错误等风险前置暴露。这个「饿汉」策略背后是工程上的取舍。
除此之外,ApplicationContext还扩展了国际化、事件发布、资源加载、环境抽象等能力,但这些不是本文重点,先按下不表。
四、容器启动:refresh() 方法是一切的开端
无论是经典ClassPathXmlApplicationContext,还是现在主流的注解驱动AnnotationConfigApplicationContext,容器启动的核心都在AbstractApplicationContext的refresh()方法里。
这个方法可谓 Spring 源码皇冠上的明珠,十几个步骤,环环相扣。我们先看它的骨架:
java
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新:记录启动时间、校验必要属性 prepareRefresh(); // 2. 获取 BeanFactory,默认是 DefaultListableBeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 预处理 BeanFactory:设置类加载器、注册一些默认后置处理器 prepareBeanFactory(beanFactory); try { // 4. 留给子类的扩展点,BeanFactory 创建后、BeanDefinition 加载前 postProcessBeanFactory(beanFactory); // 5. 执行 BeanFactoryPostProcessor:核心是解析配置、注册 BeanDefinition invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册 BeanPostProcessor registerBeanPostProcessors(beanFactory); // 7. 初始化消息源、事件广播器等 initMessageSource(); initApplicationEventMulticaster(); onRefresh(); // 8. 注册事件监听器 registerListeners(); // 9. 实例化所有非懒加载的单例 Bean finishBeanFactoryInitialization(beanFactory); // 10. 完成刷新,发布 ContextRefreshedEvent finishRefresh(); } catch (BeansException ex) { destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }这段代码是整个 Spring 容器运行的地基,建议你背下这十几个步骤的顺序和职责。其中和「Bean 加载过程」关系最密切的有三个:
第 5 步
invokeBeanFactoryPostProcessors:完成 BeanDefinition 的扫描和注册。没有这一步,后面「图纸」都不存在,更谈不上创建 Bean。第 6 步
registerBeanPostProcessors:把 BeanPostProcessor 本身注册到容器里,为后续所有 Bean 的创建提供拦截点。第 9 步
finishBeanFactoryInitialization:正式触发所有非懒加载单例 Bean 的创建。我们日常最关心的「加载过程」主要就发生在这里。
这三步的时间先后非常关键,直接决定了后面的几个高频问题:
为什么
@Configuration配置类能先被解析?因为它依赖第 5 步的ConfigurationClassPostProcessor。为什么 BeanPostProcessor 本身要在 Bean 之前注册?因为它要先去拦截别人,自己必须先生效。
为什么我们自定义的 Bean 都是在 refresh 的第 9 步统一创建,而不是用到才创建?因为单例 + 非懒加载的默认策略就是启动时全部实例化。
理解了 refresh 的骨架,Bean 加载过程就有了「全局坐标系」。接下来我们逐个阶段深入。
五、第一阶段:扫描与注册,把配置变成一张张「图纸」
5.1 ConfigurationClassPostProcessor 的登场
refresh 的第 5 步invokeBeanFactoryPostProcessors会执行所有已注册的BeanFactoryPostProcessor。其中有一个内置的、极其重要的实现:ConfigurationClassPostProcessor。
它是注解驱动开发的核心引擎,负责做两件事:
解析
@Configuration标注的配置类;扫描
@ComponentScan指定的包下的@Component、@Service、@Repository、@Controller等注解,并把它们注册成 BeanDefinition。
举个最简单的例子,下面这个启动类:
java
@Configuration @ComponentScan("com.example.service") public class AppConfig { @Bean public DataSource dataSource() { return new HikariDataSource(); } }在ConfigurationClassPostProcessor处理过后,容器里会多出来两类 BeanDefinition:一类来自@ComponentScan扫描到的普通组件,一类来自@Bean方法声明的DataSource。
这里有个细节值得记住:@Bean方法的解析不只是「注册一个 Bean」,它还会把方法封装成一个ConfigurationClassBeanDefinition,并记录它由哪个配置类产生。正因为记录了「出厂信息」,Spring 才能在后续通过工厂方法(而不是反射构造器)来创建这个 Bean。
5.2 注册的终点:DefaultListableBeanFactory
扫描出来的 BeanDefinition 最终都注册到一个BeanDefinitionRegistry里。默认实现就是DefaultListableBeanFactory,它同时实现了BeanFactory和BeanDefinitionRegistry两个角色,堪称「既管图纸,又管成品」的集大成者。
它的内部用一个ConcurrentHashMap保存所有 BeanDefinition:
java
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
同时它还会维护一张String -> Class的别名映射表,用来处理「一个接口多个实现」时按名字精确匹配的场景。这正是后面要讲的getBean按名称查找的底层支撑。
到了这一步,「图纸」已经就位。但图纸还不是最终施工方案,因为 BeanDefinition 支持父子继承、抽象定义等特性,所以还需要一次「合并」。
六、第二阶段:合并 BeanDefinition,补齐最终施工方案
Spring 的 BeanDefinition 允许继承。比如一个抽象的父定义规定了公共属性,具体的子定义只写差异部分。为了避免每次使用都去递归查找父定义,Spring 引入了合并 BeanDefinition(merged bean definition)的概念。
核心入口是AbstractBeanFactory#getMergedLocalBeanDefinition,它会判断:
如果当前 BeanDefinition 没有父定义,也不是抽象定义,直接返回它本身;
如果有父定义,就把父定义和子定义合并成一个全新的
RootBeanDefinition并缓存起来,下次直接取缓存。
缓存存放在DefaultListableBeanFactory的mergedBeanDefinitions这个 map 中。
这个过程为什么要单拎出来讲?因为面试官追问「BeanDefinition 合并是干嘛的」时,很多候选人答不上来。其实它的价值有两个:
性能:把继承链的查找结果一次性算好并缓存,避免每次 getBean 都重复解析;
完整性:保证后续实例化、注入时拿到的永远是「合并后的最终配置」,逻辑更简单,不需要再关心父子关系。
合并完成后,下一步就正式进入「按图施工」阶段。Spring 提供了很多扩展点,允许我们在真正new对象之前插手。
七、第三阶段:实例化,从图纸到毛坯对象
7.1 实例化前还有一次拦截机会
很多人以为createBean一进来就直接反射new,其实 Spring 在真正实例化之前,给了一个拦截点:InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation。
它的调用发生在AbstractAutowireCapableBeanFactory#createBean中,源码简化后是这样的:
java
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) { RootBeanDefinition mbdToUse = mbd; // 解析 beanClass Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // 给 InstantiationAwareBeanPostProcessor 一个提前返回代理对象的机会 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } // 正常走实例化流程 Object beanInstance = doCreateBean(beanName, mbdToUse, args); return beanInstance; }这里的resolveBeforeInstantiation会依次调用两个后置处理器方法:
postProcessBeforeInstantiation;postProcessAfterInitialization(如果前面那个方法返回了非空对象)。
如果这一步返回了非空对象,Spring 会认为「你已经替我把 Bean 创建好了」,于是直接跳过后面整个正常创建流程。这个机制能做什么?最典型的应用就是把目标 Bean 替换成代理对象,比如一些框架用它来实现 AOP 的提前介入,在目标对象还没创建时就返回一个代理。
在实际开发中我们很少直接用到它,但面试时如果能把这一层说清楚,会让面试官觉得你不是只背了「三段式」。
7.2 实例化的三种方式
跳过resolveBeforeInstantiation之后,Bean 就正式进入实例化。真正执行这一步的是AbstractAutowireCapableBeanFactory#createBeanInstance。Spring 在这里并不只有new一种选择,它支持三种实例化方式:
构造器实例化:最常用的方式,通过反射调用有参或无参构造器创建对象;
工厂方法实例化:
@Bean方法本质上就是工厂方法,也可以是静态工厂方法或实例工厂方法;Supplier 实例化:通过 Supplier 函数式接口提供实例,常见于编程式注册 Bean 的场景。
我们先看createBeanInstance的简化骨架:
java
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 解析 beanClass Class<?> beanClass = resolveBeanClass(mbd, beanName); // 2. 如果指定了 Supplier,直接调用 Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } // 3. 如果配置了工厂方法,通过工厂方法创建 if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // 4. 根据构造器创建:优先选择被 @Autowired 标注或有参解析的构造器 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) { return autowireConstructor(beanName, mbd, ctors, args); } // 5. 默认无参构造器 return instantiateBean(beanName, mbd); }几个高频考点藏在上面这段代码里:
@Autowired标注构造器时,为什么可以省略@Autowired?因为 Spring 在只有一个构造器时,默认把它当作注入点,这就是很多人既熟悉又说不清的一条规则。@Bean方法为什么不需要反射调用构造器?因为它在第一步扫描阶段就被记录成了工厂方法,实例化时直接走instantiateUsingFactoryMethod。Supplier 从哪里来?典型场景是
context.registerBean(UserService.class, () -> new UserService()),它绕开依赖注入和代理逻辑,适合非常简单的 Bean。
到了这一步,一个「毛坯对象」已经诞生,但它内部的依赖还是空的。接下来进入 Spring 最复杂、也是面试最常深挖的阶段:属性填充。
八、第四阶段:属性填充(依赖注入的核心)
8.1 populateBean:属性填充的主流程
实例化完成之后,Spring 会拿到一个「裸对象」。这个对象还不能直接用,因为它的成员变量都还空着。下一步就是populateBean,它负责把依赖、属性值注入到对象中。
主流程概括起来有三层:
判断是否需要继续填充:如果 BeanDefinition 中配置了
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation返回 false,则直接停止填充;根据
byName或byType等自动装配模式处理依赖;调用
InstantiationAwareBeanPostProcessor#postProcessProperties处理注解注入,最后应用PropertyValues中的属性值。
其中第三步是全场的灵魂。我们平时写的@Autowired、@Value、@Resource,并不是由new的反射机制自动完成的,而是由两类后置处理器在populateBean阶段完成的:
AutowiredAnnotationBeanPostProcessor:处理@Autowired和@Value;CommonAnnotationBeanPostProcessor:处理 JSR-250 注解,包括@Resource、@PostConstruct、@PreDestroy。
所以面试中再被问到「@Autowired是怎么生效的」,不要只说「Spring 会注入」,而应该说:Bean 在populateBean阶段被AutowiredAnnotationBeanPostProcessor扫描到注入点,再调用resolveDependency找到依赖 Bean 完成赋值。
8.2 @Autowired 与 @Resource:一个是类型,一个是名字
这道题几乎每年都在考,关键差异如下:
提供方不同:
@Autowired属于 Spring 框架;@Resource属于 Java 标准 JSR-250。匹配策略不同:
@Autowired默认按类型 byType 解析,遇到多个候选时再按@Qualifier或字段名匹配;@Resource默认先按名称 byName 解析,找不到再退回按类型。可选性处理不同:
@Autowired默认要求依赖必须存在,否则会报错,需要配合required = false;@Resource更宽容一些。
用一个例子会更好理解。假设容器里有UserMapper和AdminMapper两个 Mapper 实现:
java
@Service public class UserService { // 按类型会找到两个 Mapper,Spring 会按字段名 userMapper 精确匹配 @Autowired private Mapper userMapper; // @Resource 默认按名字 lookupUserMapper 查找,找不到再按类型退回 @Resource(name = "lookupUserMapper") private Mapper lookUpMapper; }这里顺便纠正一个常见误区:@Autowired匹配字段时并不像@Resource那样默认按字段名,它在类型不唯一后,才会以「字段名」作为 fallback 去 byName 兜底。把「类型优先、名称兜底」这个顺序记牢,能帮你应付大量追问。
九、第五阶段:初始化,Bean 的「成人礼」
9.1 initializeBean:初始化阶段的完整骨架
依赖注入完成后,Bean 对象已经「五脏俱全」,但 Spring 还给了它一次「成人礼」。入口是initializeBean:
java
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 1. 触发 Aware 回调,让 Bean 感知容器 invokeAwareMethods(beanName, bean); // 2. BeanPostProcessor 前置处理,返回的对象可能被包装 Object wrappedBean = bean; if (mbd != null && !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } // 3. 执行初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); // 4. BeanPostProcessor 后置处理,AOP 代理主要在这里生成 if (mbd != null && !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }这张图把初始化阶段切成四块,每一块都能单拎出来考你。
9.2 Aware 回调:让 Bean 感知容器
普通 Bean 默认是「无感知」的,它不知道自己的名字,也不知道容器的存在。但有时候我们确实需要这些信息。Spring 通过 Aware 系列接口提供回调:
BeanNameAware#setBeanName:拿到当前 Bean 的名字;BeanFactoryAware#setBeanFactory:拿到所属的 BeanFactory;ApplicationContextAware#setApplicationContext:拿到 ApplicationContext。
注意它们的执行位置并不同。BeanNameAware和BeanFactoryAware由invokeAwareMethods直接调用,而ApplicationContextAware其实是由ApplicationContextAwareProcessor这个 BeanPostProcessor 处理的。所以当你看到实现ApplicationContextAware却没触发时,很可能是没有把它交给容器管理,这是经典踩坑题。
9.3 初始化三件套的执行顺序
初始化方法是 Spring 最靓的考点之一。它有三种来源:JSR-250 的@PostConstruct、Spring 的InitializingBean#afterPropertiesSet和 XML 或注解里的init-method。
它们的执行顺序是固定的:
@PostConstruct标注的方法;InitializingBean#afterPropertiesSet;init-method指定的方法。
为什么是这个顺序?因为 Spring 源码里的invokeInitMethods先检查当前对象是否是InitializingBean,而@PostConstruct作为 JSR-250 注解,由更早注册的CommonAnnotationBeanPostProcessor在 BeanPostProcessor 前置阶段执行。把装配和执行顺序理清,胜过死记硬背。
9.4 后置处理与 AOP 代理的诞生
初始化完成后,Spring 会执行BeanPostProcessor#postProcessAfterInitialization。如果你熟悉 AOP,会知道切面代理主要就是在这个阶段生成的。核心逻辑由AbstractAutoProxyCreator完成,它会判断当前 Bean 是否需要被增强:
如果需要,就返回一个 JDK 动态代理对象,或 CGLIB 生成的子类代理对象;
如果不需要,就原样返回。
这里有一个面试高频陷阱:bean 从getBean返回的最终对象,可能和最初new出来的对象不是同一个。因为后置处理会把它包装成代理。理解了这一点,你就能解释为什么某些通过this调用的方法不会走 AOP 增强,因为this指向的是原始对象,而不是容器中的代理对象。
十、BeanPostProcessor:贯穿始终的幕后灵魂
10.1 接口与扩展体系
讲到这里,你会发现每个阶段都离不开BeanPostProcessor。它的接口非常简单:
java
public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }再往下,InstantiationAwareBeanPostProcessor又扩展出实例化前后的拦截点,比如前面讲过的postProcessBeforeInstantiation和postProcessProperties。可以说,Spring 把「创建 Bean」这件原本很单调的事情,拆成了一个高度可插拔的流水线,而BeanPostProcessor就是流水线上每个工位的开关。
10.2 常见实现与工作时机
把几个重要的实现和工作时机对应清楚,面试时就能脱口而出:
AutowiredAnnotationBeanPostProcessor:在属性填充阶段处理@Autowired和@Value;CommonAnnotationBeanPostProcessor:处理@Resource、@PostConstruct、@PreDestroy;ApplicationContextAwareProcessor:处理ApplicationContextAware等回调;AbstractAutoProxyCreator:在初始化之后生成 AOP 代理。
这也是为什么在refresh()里要先执行registerBeanPostProcessors:这些处理器必须比普通 Bean 更早准备好,否则普通 Bean 创建时就没有「流水线工人」了。
十一、循环依赖与三级缓存:面试必备的硬核考点
11.1 什么是循环依赖
循环依赖就是 A 依赖 B、B 又依赖 A。典型代码如下:
java
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }如果 Spring 只能一步步「A 创建完再创建 B,B 创建完再创建 A」,就会陷入死循环。Spring 的做法是先创建出 A 的早期引用,即使 A 还没结束初始化,也先把引用暴露出去,B 拿到这个引用后继续完成自己的创建。
11.2 三级缓存各自存什么
Spring 在DefaultSingletonBeanRegistry中维护了三个 Map:
一级缓存
singletonObjects:存放已经完全创建好的单例 Bean;二级缓存
earlySingletonObjects:存放已经实例化、但尚未完成初始化的早期 Bean 对象;三级缓存
singletonFactories:存放能创建早期 Bean 引用的 ObjectFactory。
java
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
11.3 三级缓存解决循环依赖的流程
创建 A 时,实例化完成后,在三级缓存中放入一个
ObjectFactory,它能返回 A 的早期引用;A 进入属性填充,发现需要依赖 B,于是去创建 B;
创建 B 时发现 B 依赖 A,此时先去一级缓存找不到 A,就去三级缓存拿到
ObjectFactory,从而得到 A 的早期引用;B 拿到 A 后完成自身创建,并被放入一级缓存;
A 继续完成依赖注入和初始化,最终也进入一级缓存。
整个过程中,A 和 B 共享的是 A 的早期引用,而不是最终成品,这就让循环依赖得以解开。
11.4 为什么需要三级缓存,而不是二级
这是面试官最爱的追问。二级缓存已经能解决普通循环依赖,为什么要再加一个singletonFactories?答案就在于AOP 代理。
假设 A 是一个需要被代理的 Bean,此时 A 还没走到初始化后置处理,也就还没有生成真正的代理对象。三级缓存中的ObjectFactory提供了延迟决定的可能:只有当真正发生循环依赖、需要提前暴露引用时,才通过getEarlyBeanReference决定是返回原始对象还是提前生成代理。这样既保证了普通 Bean 不提前做代理,又保证了循环依赖场景下拿到的是正确的对象。
如果把三级缓存省掉,直接用二级缓存暴露对象,就会出现两种尴尬:要么普通 Bean 提前暴露被代理,导致重复代理;要么有代理需求时暴露的是原始对象,破坏了 AOP 语义。三级缓存的存在,本质上是「延迟决策」这一设计思想的体现。
11.5 构造器注入的循环依赖为什么解决不了
因为三级缓存的前提是Bean 已经完成实例化。而构造器注入的循环依赖要求「A 的构造器需要 B,B 的构造器需要 A」,此时 A 还没被实例化出来,自然无法暴露早期引用,Spring 只能抛BeanCurrentlyInCreationException。
解决办法有三种:改用字段注入或 setter 注入;在构造器参数上加@Lazy延迟初始化;重构设计,把循环依赖的两部分拆到第三个 Bean 中。
十二、getBean 到 createBean:源码调用链一次走通
前面各个阶段都是「拆开讲」,最后我们把从getBean到createBean的调用链再串一遍。
text
AbstractBeanFactory#getBean ↓ doGetBean ↓ 先从缓存中找(一级、二级、三级) ↓ 找不到则进入 createBean AbstractAutowireCapableBeanFactory#createBean ↓ resolveBeforeInstantiation(给 InstantiationAwareBeanPostProcessor 一次提前返回机会) ↓ doCreateBean ↓ createBeanInstance(实例化) ↓ addSingletonFactory(把早期引用工厂放入三级缓存) ↓ populateBean(属性填充) ↓ initializeBean(初始化) ↓ 返回最终对象并放入一级缓存
把这条链路背下来,面试官顺着问哪一段你都能接上话:从缓存策略、实例化方式、属性填充的后置处理器、初始化三件套,到循环依赖和代理创建,全都在同一条主线上。
十三、常见追问与答题思路
13.1 为什么 BeanPostProcessor 要单独先注册?
因为普通 Bean 创建时要用到它们。比如@Autowired的注入依赖AutowiredAnnotationBeanPostProcessor,AOP 代理依赖AbstractAutoProxyCreator。如果 BeanPostProcessor 和普通 Bean 混在一起创建,就会出现「还没工人就先造产品」的悖论。
13.2 单例 Bean 会被创建几次?
默认只创建一次。单例 Bean 在一级缓存中命中后直接返回,不会重复实例化。但要注意,如果同一个类同时被注册成多个 Bean 名称,会出现多个实例。
13.3 @Lazy 是怎么绕过循环依赖的?
@Lazy会为注入点生成一个代理对象,实际调用时才真正去容器里取目标 Bean。这样在创建 A 时,B 注入的其实是一个代理,A 可以顺利完成创建;等 B 真正被使用的时候,A 已经在容器中就绪,循环依赖自然被打破。
13.4 工厂方法创建的 Bean 也会走完整生命周期吗?
会。@Bean方法创建的 Bean 同样会经历属性填充、Aware 回调、初始化三件套、后置处理等完整流程,唯一的区别是「实例化」这一步走的是工厂方法而不是构造器。
13.5 什么时候会用到 InstantiationAwareBeanPostProcessor?
常见于框架层和高级定制,例如:
需要在目标 Bean 创建之前就返回代理对象;
需要在属性填充之后、初始化之前做额外处理;
需要跳过默认的属性填充逻辑。
在普通业务开发中很少直接使用,但理解它能帮你把 Spring 的扩展点体系串起来。
十四、总结
「讲讲 Bean 的加载过程」这道题看似基础,实际串联了 Spring 从容器启动、BeanDefinition 注册、实例化、依赖注入、初始化、后置处理到 AOP 代理生成的一整套机制。回答得好,能证明你不仅会用 Spring,更理解它的设计取舍。
把这篇文章的主线再浓缩一遍:
BeanDefinition 是图纸,Bean 是成品,图纸先注册、再合并、最后按图施工。
refresh 是总开关,第 5 步扫描配置、第 6 步注册后置处理器、第 9 步统一创建单例 Bean。
实例化三方式:构造器、工厂方法、Supplier。
属性填充是依赖注入的主场,
@Autowired和@Resource分别由不同的 BeanPostProcessor 处理。初始化三件套有固定顺序:
@PostConstruct→InitializingBean→init-method。后置处理是 AOP 的起点,最终放回容器中的对象可能是代理对象而不是原始对象。
三级缓存解决的是延迟决策问题,不是为了「多存一份」,而是为了在合适时机决定是否提前暴露代理。
循环依赖的解法依赖于先实例化再注入,构造器注入无法打破这个约束,需要
@Lazy或重构。