面试官:讲讲 Bean 的加载过程
一、开篇面试官问 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 ↓ 合并 BeanDefinitionmergeBeanDefinition ↓ 实例化前拦截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被调用时才真正创建 BeanApplicationContext在容器启动阶段就会把单例、非懒加载的 Bean 全部提前创建好也就是我们常说的「启动时预实例化pre-instantiate」。这是它和BeanFactory最典型的差异之一。这也引出了一个重要的面试点为什么 Spring Boot 启动时创建了那么多单例 Bean 却感觉不到明显的性能问题因为启动阶段本身就是一次性成本提前创建可以保证运行期的稳定性同时把循环依赖、配置错误等风险前置暴露。这个「饿汉」策略背后是工程上的取舍。除此之外ApplicationContext还扩展了国际化、事件发布、资源加载、环境抽象等能力但这些不是本文重点先按下不表。四、容器启动refresh() 方法是一切的开端无论是经典ClassPathXmlApplicationContext还是现在主流的注解驱动AnnotationConfigApplicationContext容器启动的核心都在AbstractApplicationContext的refresh()方法里。这个方法可谓 Spring 源码皇冠上的明珠十几个步骤环环相扣。我们先看它的骨架javapublic 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。举个最简单的例子下面这个启动类javaConfiguration 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保存所有 BeanDefinitionjavaprivate final MapString, BeanDefinition beanDefinitionMap new ConcurrentHashMap(256); private volatile ListString beanDefinitionNames new ArrayList(256);同时它还会维护一张String - Class的别名映射表用来处理「一个接口多个实现」时按名字精确匹配的场景。这正是后面要讲的getBean按名称查找的底层支撑。到了这一步「图纸」已经就位。但图纸还不是最终施工方案因为 BeanDefinition 支持父子继承、抽象定义等特性所以还需要一次「合并」。六、第二阶段合并 BeanDefinition补齐最终施工方案Spring 的 BeanDefinition 允许继承。比如一个抽象的父定义规定了公共属性具体的子定义只写差异部分。为了避免每次使用都去递归查找父定义Spring 引入了合并 BeanDefinitionmerged bean definition的概念。核心入口是AbstractBeanFactory#getMergedLocalBeanDefinition它会判断如果当前 BeanDefinition 没有父定义也不是抽象定义直接返回它本身如果有父定义就把父定义和子定义合并成一个全新的RootBeanDefinition并缓存起来下次直接取缓存。缓存存放在DefaultListableBeanFactory的mergedBeanDefinitions这个 map 中。这个过程为什么要单拎出来讲因为面试官追问「BeanDefinition 合并是干嘛的」时很多候选人答不上来。其实它的价值有两个性能把继承链的查找结果一次性算好并缓存避免每次 getBean 都重复解析完整性保证后续实例化、注入时拿到的永远是「合并后的最终配置」逻辑更简单不需要再关心父子关系。合并完成后下一步就正式进入「按图施工」阶段。Spring 提供了很多扩展点允许我们在真正new对象之前插手。七、第三阶段实例化从图纸到毛坯对象7.1 实例化前还有一次拦截机会很多人以为createBean一进来就直接反射new其实 Spring 在真正实例化之前给了一个拦截点InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation。它的调用发生在AbstractAutowireCapableBeanFactory#createBean中源码简化后是这样的javaprotected 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会依次调用两个后置处理器方法postProcessBeforeInstantiationpostProcessAfterInitialization如果前面那个方法返回了非空对象。如果这一步返回了非空对象Spring 会认为「你已经替我把 Bean 创建好了」于是直接跳过后面整个正常创建流程。这个机制能做什么最典型的应用就是把目标 Bean 替换成代理对象比如一些框架用它来实现 AOP 的提前介入在目标对象还没创建时就返回一个代理。在实际开发中我们很少直接用到它但面试时如果能把这一层说清楚会让面试官觉得你不是只背了「三段式」。7.2 实例化的三种方式跳过resolveBeforeInstantiation之后Bean 就正式进入实例化。真正执行这一步的是AbstractAutowireCapableBeanFactory#createBeanInstance。Spring 在这里并不只有new一种选择它支持三种实例化方式构造器实例化最常用的方式通过反射调用有参或无参构造器创建对象工厂方法实例化Bean方法本质上就是工厂方法也可以是静态工厂方法或实例工厂方法Supplier 实例化通过 Supplier 函数式接口提供实例常见于编程式注册 Bean 的场景。我们先看createBeanInstance的简化骨架javaprotected 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和ValueCommonAnnotationBeanPostProcessor处理 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 falseResource更宽容一些。用一个例子会更好理解。假设容器里有UserMapper和AdminMapper两个 Mapper 实现javaService public class UserService { // 按类型会找到两个 MapperSpring 会按字段名 userMapper 精确匹配 Autowired private Mapper userMapper; // Resource 默认按名字 lookupUserMapper 查找找不到再按类型退回 Resource(name lookupUserMapper) private Mapper lookUpMapper; }这里顺便纠正一个常见误区Autowired匹配字段时并不像Resource那样默认按字段名它在类型不唯一后才会以「字段名」作为 fallback 去 byName 兜底。把「类型优先、名称兜底」这个顺序记牢能帮你应付大量追问。九、第五阶段初始化Bean 的「成人礼」9.1 initializeBean初始化阶段的完整骨架依赖注入完成后Bean 对象已经「五脏俱全」但 Spring 还给了它一次「成人礼」。入口是initializeBeanjavaprotected 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拿到所属的 BeanFactoryApplicationContextAware#setApplicationContext拿到 ApplicationContext。注意它们的执行位置并不同。BeanNameAware和BeanFactoryAware由invokeAwareMethods直接调用而ApplicationContextAware其实是由ApplicationContextAwareProcessor这个 BeanPostProcessor 处理的。所以当你看到实现ApplicationContextAware却没触发时很可能是没有把它交给容器管理这是经典踩坑题。9.3 初始化三件套的执行顺序初始化方法是 Spring 最靓的考点之一。它有三种来源JSR-250 的PostConstruct、Spring 的InitializingBean#afterPropertiesSet和 XML 或注解里的init-method。它们的执行顺序是固定的PostConstruct标注的方法InitializingBean#afterPropertiesSetinit-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。它的接口非常简单javapublic 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和ValueCommonAnnotationBeanPostProcessor处理Resource、PostConstruct、PreDestroyApplicationContextAwareProcessor处理ApplicationContextAware等回调AbstractAutoProxyCreator在初始化之后生成 AOP 代理。这也是为什么在refresh()里要先执行registerBeanPostProcessors这些处理器必须比普通 Bean 更早准备好否则普通 Bean 创建时就没有「流水线工人」了。十一、循环依赖与三级缓存面试必备的硬核考点11.1 什么是循环依赖循环依赖就是 A 依赖 B、B 又依赖 A。典型代码如下javaComponent public class A { Autowired private B b; } Component public class B { Autowired private A a; }如果 Spring 只能一步步「A 创建完再创建 BB 创建完再创建 A」就会陷入死循环。Spring 的做法是先创建出 A 的早期引用即使 A 还没结束初始化也先把引用暴露出去B 拿到这个引用后继续完成自己的创建。11.2 三级缓存各自存什么Spring 在DefaultSingletonBeanRegistry中维护了三个 Map一级缓存singletonObjects存放已经完全创建好的单例 Bean二级缓存earlySingletonObjects存放已经实例化、但尚未完成初始化的早期 Bean 对象三级缓存singletonFactories存放能创建早期 Bean 引用的 ObjectFactory。javaprivate final MapString, Object singletonObjects new ConcurrentHashMap(256); private final MapString, Object earlySingletonObjects new HashMap(16); private final MapString, 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 的构造器需要 BB 的构造器需要 A」此时 A 还没被实例化出来自然无法暴露早期引用Spring 只能抛BeanCurrentlyInCreationException。解决办法有三种改用字段注入或 setter 注入在构造器参数上加Lazy延迟初始化重构设计把循环依赖的两部分拆到第三个 Bean 中。十二、getBean 到 createBean源码调用链一次走通前面各个阶段都是「拆开讲」最后我们把从getBean到createBean的调用链再串一遍。textAbstractBeanFactory#getBean ↓ doGetBean ↓ 先从缓存中找一级、二级、三级 ↓ 找不到则进入 createBean AbstractAutowireCapableBeanFactory#createBean ↓ resolveBeforeInstantiation给 InstantiationAwareBeanPostProcessor 一次提前返回机会 ↓ doCreateBean ↓ createBeanInstance实例化 ↓ addSingletonFactory把早期引用工厂放入三级缓存 ↓ populateBean属性填充 ↓ initializeBean初始化 ↓ 返回最终对象并放入一级缓存把这条链路背下来面试官顺着问哪一段你都能接上话从缓存策略、实例化方式、属性填充的后置处理器、初始化三件套到循环依赖和代理创建全都在同一条主线上。十三、常见追问与答题思路13.1 为什么 BeanPostProcessor 要单独先注册因为普通 Bean 创建时要用到它们。比如Autowired的注入依赖AutowiredAnnotationBeanPostProcessorAOP 代理依赖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或重构。

相关新闻

STM32 SDIO 4bit模式下HAL_SD_ConfigWideBusOperation卡死原因与排查

STM32 SDIO 4bit模式下HAL_SD_ConfigWideBusOperation卡死原因与排查

先说结论:这个问题十有八九不是你代码写错了,而是HAL_SD_ConfigWideBusOperation这个函数在4bit模式下,对SD卡的初始化时序、时钟分频和硬件连接非常敏感。我当初调这个问题调了整整一个下午,最后发现罪魁祸首竟然是CubeMX里一个不…

2026/10/3 14:55:14 阅读更多 →
64位系统下断针即时打印:Access驱动与打印链路完整落地

64位系统下断针即时打印:Access驱动与打印链路完整落地

简介:这份资源是一款面向针式打印机用户的断针免修与打印优化工具,专为仍在使用针式打印机处理票据、单据、多联纸打印的办公与财务场景打造,尤其适合遇到打印针断裂、打印质量下降却不想送修或更换设备的用户。压缩包为zip格式,整…

2026/10/3 14:55:14 阅读更多 →
OpenShell使用指南:为Windows 11找回经典开始菜单

OpenShell使用指南:为Windows 11找回经典开始菜单

如果你是从Windows 7时代一路用过来的老用户,升级到Windows 11之后的第一反应八成和我一样:开始菜单怎么变成这样了?点开全是推荐内容,想找控制面板要再多点好几次。我去年换了新笔电,系统自带Windows 11,用…

2026/10/3 14:55:13 阅读更多 →

最新新闻

水稻病虫害识别系统源码实战:Python机器学习从训练到部署

水稻病虫害识别系统源码实战:Python机器学习从训练到部署

简介:这份资源是基于Python机器学习的水稻病虫害自动识别系统源码包,面向农学信息化方向的学生、课程设计开发者及希望入门图像分类实战的工程师,用于解决水稻病虫害人工识别效率低、经验依赖强的问题。压缩包共310个文件,约2.56M…

2026/10/3 15:26:08 阅读更多 →
QuickBlue AI应用底座:企业大模型落地与实战指南

QuickBlue AI应用底座:企业大模型落地与实战指南

1. QuickBlue 到底是什么先给结论:QuickBlue 不是一个具体的业务软件,也不是某个大模型的名字,而是一套专门给企业做 AI 应用落地用的“中间层平台”。你可以把它理解成企业 AI 时代的“水电煤接口”——它不直接生产水电煤,但它让…

2026/10/3 15:26:07 阅读更多 →
密度算符完全指南:从混合态到量子噪声与纠缠判定

密度算符完全指南:从混合态到量子噪声与纠缠判定

做量子计算方向的人,尤其是刚开始啃量子信息理论的同学,几乎都会在某个阶段撞上同一个坎:前面几章还在用波函数 |ψ⟩ 描述一切,到了讲量子噪声、开放系统、部分测量、纠缠判定的时候,所有公式突然都换成了 ρ&#xf…

2026/10/3 15:26:07 阅读更多 →
UE5、3A与开放世界:从刀光材质到网络同步的技术拆解

UE5、3A与开放世界:从刀光材质到网络同步的技术拆解

1. 从一场发布会聊起:UE5、3A、开放世界到底在说什么如果你最近刷到过腾讯游戏发布会的相关消息,大概率会被几个词反复轰炸:UE5、3A、开放世界。这三个词放在一起,基本就是当下游戏行业最顶配的“技术三件套”。但很多人看完发布会…

2026/10/3 15:26:07 阅读更多 →
Arch Linux双系统安装全指南:从分区到引导修复避坑实战

Arch Linux双系统安装全指南:从分区到引导修复避坑实战

看过太多新手在Arch Linux双系统上翻车,有的把Windows引导搞没了,有的分区时手一抖把整个盘抹了,还有的装完进不去桌面只能干瞪眼。这篇文章直接把我踩过的坑和验证过的流程全写出来,从Windows下分区、做启动盘、改BIOS&#xff0…

2026/10/3 15:26:06 阅读更多 →
TSMaster Python二次开发:CAN/CANFD总线采集与周期告警实战

TSMaster Python二次开发:CAN/CANFD总线采集与周期告警实战

上个月接了一台新能源样车的数据采集任务,甲方要求把整车上CAN和CANFD两条总线跑的关键报文实时抓下来,还要能看到发动机转速、车速、电池SOC,一旦发现某个报文周期异常就自动弹提示。如果用老办法一边开TSMaster界面一边盯监控窗口&#xff…

2026/10/3 15:25:06 阅读更多 →

日新闻

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南 【免费下载链接】ex-skill 前任 skill 项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill 前任.skill 是一个运行在 Claude Code 上的开源 Skill:导入微信、iMessage、短信、…

2026/10/3 0:00:27 阅读更多 →
45个经典Linux面试题:从命令到网络排障的完整考点解析

45个经典Linux面试题:从命令到网络排障的完整考点解析

刚开始带应届生的时候,我最头疼的就是他们拿着一摞Linux面试题背得滚瓜烂熟,一上机全露馅。后来自己从被面的人变成面别人的人,才慢慢摸清楚:Linux面试题考的根本不是答案本身,而是你面对一个不确定的系统问题时&#…

2026/10/3 0:01:28 阅读更多 →
SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

简介:本资源是一份面向SAP ABAP开发人员、生产计划专员及ERP实施顾问的实操型操作指南,聚焦SAP生产预留核心业务场景,系统解决物料预留创建、查询、校验与批量处理等高频问题。文档以结构化方式覆盖预留背景原理、OMC2编码规则、工厂级参数配…

2026/10/3 0:01:28 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/3 9:14:33 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/3 9:47:50 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/3 9:42:31 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 10:36:31 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →