ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【面朝大厂】面试官:说说 Spring Bean 的实例化过程?面试必问的!2万字详解

【面朝大厂】面试官:说说 Spring Bean 的实例化过程?面试必问的!2万字详解 摘要本文以 Spring IoC 容器中最核心的 Bean 实例化过程为主线从 BeanDefinition 注册、getBean 入口、createBean 调度、doCreateBean 执行到构造器实例化、属性填充、初始化、AOP 代理与循环依赖结合源码级讲解和面试回答模板帮助读者把零散知识点串成完整闭环。一、为什么面试官总爱问 Bean 的实例化过程在很多人的认知里Spring 只是一个用来做依赖注入、管理对象的框架我写一个Service、ComponentSpring 帮我自动创建对象我用的时候直接拿出来就行。这种理解在做业务开发时或许够用但一旦进入大厂面试就很容易暴露出对 Spring 底层机制的理解停留在「会用」的层面。面试官之所以反复追问「Spring Bean 的实例化过程」本质上是在考察候选人三个层次的能力第一层框架理解能力你是否真的清楚一个 Bean 从定义到真正可以使用的完整链路而不是只知道「Spring 会帮我们 new 一个对象」。第二层源码阅读能力你是否能说出getBean、createBean、doCreateBean这些关键方法之间的关系能否理解 Spring 为什么要设计这么多抽象层次。第三层工程实践能力你是否理解循环依赖如何解决、三级缓存为什么存在、AOP 代理对象何时生成以及这些机制在生产环境可能引发什么问题。因此这道题看似简单实则是 Spring 源码面试的「总开关」。把 Bean 的实例化过程讲清楚后面的生命周期、循环依赖、AOP、事务、扩展点等话题都会顺理成章地串起来。本文会从最基础的概念入手逐步深入到源码实现最后给出面试回答框架和常见追问争取做到「一篇讲透」。二、先理解什么是 Bean什么是 BeanDefinition在讨论实例化过程之前必须先区分两个极其重要、却又经常被混淆的概念Bean和BeanDefinition。2.1 Bean 是最终产物Bean就是由 Spring IoC 容器管理的对象。它和我们平时自己new出来的普通 Java 对象最大的区别在于Bean 的生命周期完全交给 Spring 容器管理包括创建、属性赋值、初始化、使用以及最终的销毁。例如下面这个对象如果交给 Spring 管理它就是一个 Beanjavapublic class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao userDao; } }当我们通过applicationContext.getBean(userService)拿到它时userDao已经被注入完成。对象本身还是那个 Java 对象但创建和管理过程由容器负责。2.2 BeanDefinition 是创建 Bean 的「配方」BeanDefinition是 Spring 用来描述一个 Bean 的元数据。它本身不是最终的对象而是记录着「如何创建一个 Bean」的所有信息可以理解成一份配方或模具。常见的核心属性包括beanClassNameBean 的全限定类名例如com.example.service.UserService。scope作用域默认是singleton还有prototype、request、session等。lazyInit是否延迟初始化。dependsOn依赖的其他 Bean 名称。propertyValues需要注入的属性值集合。constructorArgumentValues构造器参数值集合。initMethodName / destroyMethodName初始化方法和销毁方法名称。abstract / primary / autowireMode是否为抽象 Bean、是否主候选以及自动装配模式等。可以这样理解BeanDefinition 是「静态定义」Bean 是「运行时实例」。Spring 容器启动时会把配置信息解析成一个个 BeanDefinition并注册到容器中当真正需要某个 Bean 时再根据这份 BeanDefinition 把对象创建出来。2.3 BeanDefinition 从哪里来开发中我们定义 Bean 的方式有很多但它们最终都会被解析成 BeanDefinitionXML 配置传统的bean iduserService classcom.example.UserService /由XmlBeanDefinitionReader解析。注解配置Component、Service、Repository、Controller由ClassPathBeanDefinitionScanner扫描并注册。Bean 方法Configuration配置类中的Bean由ConfigurationClassPostProcessor解析。Import、ImportSelector、ImportBeanDefinitionRegistrar用于引入第三方组件或动态注册。Spring Boot 自动配置通过EnableAutoConfiguration和spring.factories或AutoConfiguration.imports中的配置类注册大量 BeanDefinition。无论来源如何容器内存中都会形成一份统一的 BeanDefinition 注册表后续的实例化全部基于这份注册表进行。三、Bean 生命周期的宏观全景在深入源码之前先对整个 Bean 生命周期建立一个宏观图景非常重要。很多人背生命周期背得很熟却不知道每一步发生在源码的哪个方法里。这里先把流程列出来后面的章节再逐段对应源码。一个单例 Bean 的完整生命周期大致可以分为以下阶段扫描与注册把配置类、注解、XML 等解析成 BeanDefinition并放入beanDefinitionMap。合并 BeanDefinition处理父子 BeanDefinition 继承关系得到RootBeanDefinition。实例化前扩展执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation可能提前返回代理对象。实例化通过构造器或工厂方法创建 Bean 的原始对象例如反射调用构造方法。合并后的后置处理执行MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition。属性填充解析并注入依赖例如Autowired、Value等字段注入、setter 注入。Aware 回调如果 Bean 实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware等接口执行对应回调。初始化前处理执行所有BeanPostProcessor的postProcessBeforeInitialization。初始化执行InitializingBean的afterPropertiesSet或配置的init-method。初始化后处理执行所有BeanPostProcessor的postProcessAfterInitialization这里通常是 AOP 创建代理对象的位置。使用Bean 进入单例池供业务代码获取和使用。销毁容器关闭时执行DisposableBean的destroy或配置的destroy-method。需要特别说明的是「实例化」在 Spring 中有两层含义。广义上它常用来泛指「从无到有创建一个可以使用的 Bean」的整个过程狭义上它专指通过构造器或工厂方法创建出原始对象、还没有进行属性填充的那一刻。面试时如果能主动区分这两个概念会让回答更有层次感。四、Spring 容器如何创建 Bean从 getBean 说起要理解 Bean 的实例化过程最好的切入点就是getBean。不管我们是显式调用context.getBean(userService)还是通过依赖注入让 Spring 自动去获取某个 Bean底层最终都会走到AbstractBeanFactory#getBean这条主链路。4.1 getBean 的整体入口getBean有多个重载方法但核心逻辑都可以追溯到doGetBean。这个方法的流程可以概括为下面几个步骤转换 Bean 名称去除前缀处理别名得到真正的 beanName。尝试从缓存获取在单例缓存中查找是否已经创建过如果能命中就直接返回。处理原型模式循环依赖如果当前正在创建这个原型 Bean直接抛出BeanCurrentlyInCreationException。处理父子容器如果当前容器没有该 BeanDefinition则委托给父容器查找。合并 BeanDefinition调用getMergedBeanDefinition获取合并后的定义。处理依赖 Bean根据dependsOn先注册并创建依赖项。按作用域创建分别处理 singleton、prototype 和其他自定义作用域。对应的核心源码结构如下javaprotected T T doGetBean(String name, ClassT requiredType, Object[] args, boolean typeCheckOnly) { String beanName transformedBeanName(name); Object bean; // 先从单例缓存中获取 Object sharedInstance getSingleton(beanName); if (sharedInstance ! null args null) { // ... 处理 FactoryBean 等逻辑 bean getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // ... 检查原型模式循环依赖、父容器、dependsOn 等 RootBeanDefinition mbd getMergedLocalBeanDefinition(beanName); // ... if (mbd.isSingleton()) { sharedInstance getSingleton(beanName, () - { return createBean(beanName, mbd, args); }); bean getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // 原型 Bean 每次创建一套新对象 bean getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { // 其他作用域 String scopeName mbd.getScope(); // ... } } return adaptBeanInstance(name, bean, requiredType); }这段代码是整个 Bean 创建流程的总调度。对于单例 Bean真正完成对象创建的是getSingleton(String beanName, ObjectFactory? singletonFactory)中的回调而回调里调用的就是createBean。4.2 getSingleton 与三级缓存的初次照面在单例 Bean 的创建路径中getSingleton承担了非常重要的工作它负责维护创建状态并把创建过程的结果放入单例缓存。javapublic Object getSingleton(String beanName, ObjectFactory? singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null) { // 记录当前 Bean 正在创建 beforeSingletonCreation(beanName); // ... singletonObject singletonFactory.getObject(); // 创建完成后加入单例池 addSingleton(beanName, singletonObject); // ... } return singletonObject; } }这里已经能隐约看到缓存的身影。Spring 用于解决循环依赖和缓存单例的结构被称为「三级缓存」一级缓存 singletonObjects存放完全创建完成的单例 Bean。二级缓存 earlySingletonObjects存放早期暴露的 Bean 对象通常是刚实例化但还未完成属性填充和初始化的对象。三级缓存 singletonFactories存放能够生成早期 Bean 引用的 ObjectFactory。关于三级缓存与循环依赖的详细机制本文会在后面的章节专门展开这里先建立印象即可。五、createBean从「创建入口」到「实例化流程」的分水岭createBean位于AbstractAutowireCapableBeanFactory中是 Bean 创建的核心调度方法。它负责决定「是走正常的创建流程还是直接被后置处理器提前替换掉」然后把真正的实例化任务交给doCreateBean。5.1 createBean 做了哪些事createBean的主要流程如下解析 Bean 的 Class根据 BeanDefinition 中的类名加载 Class 对象。准备方法覆盖处理lookup-method和replace-method等配置。实例化前处理调用InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation如果返回了非空对象则跳过常规实例化。执行 doCreateBean没有提前被替换时进入真正的 Bean 创建流程。实例化后处理调用postProcessAfterInitialization完成最终增强。源码大致如下javaprotected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) { RootBeanDefinition mbdToUse mbd; Class? resolvedClass resolveBeanClass(mbd, beanName); // ... // 给 BeanPostProcessor 一个机会提前返回代理对象 Object bean resolveBeforeInstantiation(beanName, mbdToUse); if (bean ! null) { return bean; } Object beanInstance doCreateBean(beanName, mbdToUse, args); return beanInstance; }其中resolveBeforeInstantiation的作用非常值得注意它会在 Bean 真正实例化之前询问所有InstantiationAwareBeanPostProcessor是否要自己创建一个对象代替后续流程。像 Spring AOP 的某些场景以及一些框架扩展都可能在这里动手脚。如果返回了非空对象后续的常规实例化就会被完全跳过。5.2 doCreateBean 是真正干活的地方doCreateBean是整个 Bean 创建流程中最核心的方法几乎把上一节提到的生命周期都串起来了。它的核心流程可以拆分为四步创建 Bean 实例调用createBeanInstance通过构造器或工厂方法得到原始对象。允许提前暴露引用判断是否需要提前暴露用于解决循环依赖。属性填充调用populateBean完成依赖注入。初始化调用initializeBean完成 Aware 回调、初始化前后置处理和初始化方法。源码骨架如下javaprotected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 创建 Bean 包装器 BeanWrapper instanceWrapper null; if (mbd.isSingleton()) { instanceWrapper this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper null) { instanceWrapper createBeanInstance(beanName, mbd, args); } Object bean instanceWrapper.getWrappedInstance(); Class? beanType instanceWrapper.getWrappedClass(); // 2. 允许后置处理器修改合并后的 BeanDefinition applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 3. 判断是否需要提前暴露用于解决循环依赖 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean)); } // 4. 初始化 Bean 实例 Object exposedObject bean; populateBean(beanName, mbd, instanceWrapper); exposedObject initializeBean(beanName, exposedObject, mbd); // 5. 循环依赖校验必要时返回早期引用 // 6. 注册销毁回调 return exposedObject; }六、createBeanInstanceBean 是怎么被 new 出来的在 doCreateBean 的第一步Spring 会调用 createBeanInstance 创建出 Bean 的原始对象。这一步只负责「造出对象」不会注入任何依赖。面试时可以把它的职责概括为根据 BeanDefinition 选择合适的实例化策略通过构造器或工厂方法得到 Bean 实例。核心源码结构如下javaprotected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { Class? beanClass resolveBeanClass(mbd, beanName); Supplier? instanceSupplier mbd.getInstanceSupplier(); if (instanceSupplier ! null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() ! null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // 根据后置处理器推断构造器 Constructor?[] ctors determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors ! null || mbd.getResolvedAutowireMode() AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // 默认使用无参构造器实例化 return instantiateBean(beanName, mbd); }从这段代码可以看出Spring 在选择实例化方式时有明确的优先级先用 Supplier其次用 Bean 或 XML 配置的工厂方法然后判断是否需要走带参构造器的 autowireConstructor最后才回到最简单的 instantiateBean 无参构造器路径。对于使用 Autowired 标注的构造器determineConstructorsFromBeanPostProcessors 会借助 AutowiredAnnotationBeanPostProcessor 找到合适的构造方法。如果存在多个构造器Spring 会根据参数数量、类型匹配度以及 Autowired(required false) 等信息选择一个「最优解」如果只有一个构造器Spring 5 之后即使没有标注 Autowired 也会默认使用它。另外需要明确一点实例化策略和 AOP 没有直接关系。instantiateBean 会优先使用 JDK 反射调用构造器只有配置了方法替换 lookup-method 或 replace-method 时才会选择 CGLIB 生成子类。不要把这里的 CGLIB 与 AOP 代理的 CGLIB 混为一谈。七、populateBean属性填充与依赖注入对象创建出来后它还是一个「空壳」字段里的 Autowired 依赖还没有被注入。这一步由 populateBean 完成是整个依赖注入真正落地的位置。简化后的流程骨架如下javaprotected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) { // 1. 实例化后处理可能直接终止属性填充 if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor ibpp) { if (!ibpp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } PropertyValues pvs mbd.getPropertyValues(); // 2. 根据 autowireMode 处理 byName / byType 自动装配 // 3. 解析 Autowired、Value、Inject 等注解 // 4. 最后统一执行属性赋值 applyPropertyValues(beanName, mbd, bw, pvs); }其中 AutowiredAnnotationBeanPostProcessor 会在属性填充阶段解析 Autowired、Value 等注解找到依赖的 Bean 后调用 getBean 完成注入。也就是说依赖注入不是发生在对象创建的那一刻而是在对象实例化之后的 populateBean 阶段。这也是为什么构造器注入通常比字段注入更推荐构造器注入在 createBeanInstance 阶段就必须拿到依赖能够更快地暴露循环依赖问题同时也能让 Bean 在构造完成后就具备完整状态。字段注入则必须先 new 出对象再在 populateBean 里反射赋值时机更靠后也更容易出现空指针问题。八、initializeBean初始化与扩展点串联依赖注入完成后Spring 会调用 initializeBean 完成 Bean 的初始化。这一步是各种扩展点最密集的地方也最容易在面试中被追问。它的顺序可以概括为Aware 回调、Before 处理、初始化方法、After 处理。javaprotected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { invokeAwareMethods(beanName, bean); Object wrappedBean bean; if (!mbd.isSynthetic()) { wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } invokeInitMethods(beanName, wrappedBean, mbd); if (!mbd.isSynthetic()) { wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }其中 invokeAwareMethods 会处理 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 三个常用 Aware 接口。像 ApplicationContextAware 则是在 ApplicationContextAwareProcessor 这个 BeanPostProcessor 中完成的所以执行顺序上通常比 BeanFactoryAware 稍早。invokeInitMethods 里会先判断 Bean 是否实现了 InitializingBean如果实现了就调用 afterPropertiesSet()随后再执行 BeanDefinition 中配置的 init-method。面试时经常被问到「如果同时定义了 PostConstruct、InitializingBean 和 init-method执行顺序是什么」答案是PostConstruct 由 CommonAnnotationBeanPostProcessor 在 postProcessBeforeInitialization 中触发最先执行其次是 InitializingBean 的 afterPropertiesSet()最后才是配置的 init-method。九、AOP 代理是在哪里生成的很多人以为 AOP 代理是在 Bean 创建早期就生成的实际上 Spring 默认把代理对象生成放在初始化之后也就是 initializeBean 的 postProcessAfterInitialization 阶段。以 AbstractAutoProxyCreator 为例它的 postProcessAfterInitialization 负责判断当前 Bean 是否需要被增强javapublic Object postProcessAfterInitialization(Object bean, String beanName) { if (bean ! null) { Object cacheKey getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) ! bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }如果 Bean 匹配到切面wrapIfNecessary 会返回基于 CGLIB 或 JDK 动态代理生成的代理对象并把代理对象放入容器后续 getBean 拿到的就是这个代理后的对象。把代理生成放在初始化之后能保证代理的是一个「已经完成字段注入和初始化」的完整对象这一点和循环依赖中的早期引用机制相互配合是整个 Spring AOP 设计的关键。十、循环依赖与三级缓存10.1 什么是循环依赖循环依赖指的是两个或多个 Bean 在创建时互相需要对方。最典型的场景是A 依赖 BB 又依赖 A。如果 Spring 不做特殊处理两者都会卡在「等你创建完我才能创建」的死锁状态最终抛出 BeanCurrentlyInCreationException。10.2 三级缓存分别解决什么Spring 用三个 Map 协作来破解这个问题singletonObjects一级缓存存放已经完全创建完成、可以直接使用的 Bean。earlySingletonObjects二级缓存存放已经实例化、但还未完成属性填充和初始化的早期 Bean 引用。singletonFactories三级缓存存放“能够生成早期 Bean 引用”的 ObjectFactory。核心获取逻辑如下javaprotected 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.earlySingletonObjects.get(beanName); if (singletonObject null) { ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }这段代码体现了三级缓存的关键协作先从一级缓存找完整 Bean找不到且该 Bean 正在创建中再从二级缓存找早期引用仍然找不到时才会从三级缓存取出 ObjectFactory生成早期引用并升级到二级缓存。10.3 循环依赖的完整拆解以 A - B - A 为例开始创建 A实例化 A 后因为 A 是单例且允许循环引用Spring 会把 A 的早期引用工厂放入三级缓存。填充 A 时发现依赖 B于是去创建 B。创建 B 时又依赖 A此时 A 正处于创建中Spring 通过三级缓存拿到 A 的早期引用注入给 B。B 完成创建并进入一级缓存A 继续完成填充、初始化和代理最终进入一级缓存。这里的三级缓存存在一个很重要的设计原因早期引用可能需要被代理对象替换。当 A 同时涉及循环依赖和 AOP 时singletonFactories 里的 ObjectFactory 会在真正需要时调用 getEarlyBeanReference尽可能提前生成代理对象保证 B 拿到的 A 和最终用户拿到的 A 是同一个代理对象。10.4 为什么构造器注入无法解决循环依赖循环依赖能解决的前提是对象先实例化后填充依赖。Spring 可以在实例化后、填充前暴露早期引用。但构造器注入要求「传入依赖才能完成实例化」也就是说 A 还没有完成构造之前无法暴露任何引用B 自然无法拿到 A。因此构造器注入导致的循环依赖无法通过三级缓存解决只能从设计上拆开循环依赖。十一、面试回答模板与常见追问11.1 一句话总括回答 Bean 实例化过程时可以先用一句话定调Spring 创建单例 Bean 的主链路是 getBean 到 createBean再由 doCreateBean 完成实例化、依赖注入和初始化三步。这句话能快速展示你对整体脉络的把握。11.2 分层展开面试回答建议按照“入口、创建、填充、初始化、增强”五个层次展开入口getBean 最终进入 doGetBean先查缓存再做作用域分发单例 Bean 最终走 getSingleton 的创建回调。创建入口回调中调用 createBean先给 InstantiationAwareBeanPostProcessor 一个提前返回的机会否则进入 doCreateBean。实例化createBeanInstance 根据构造器或工厂方法创建原始对象此阶段不注入依赖。填充和初始化populateBean 完成依赖注入initializeBean 依次执行 Aware、初始化前后置处理和初始化方法。代理与缓存postProcessAfterInitialization 中可能生成 AOP 代理对象最终进入一级缓存供后续使用。11.3 常见追问三级缓存为什么是三级二级不够吗核心在于 AOP 代理时机。三级缓存存的是 ObjectFactory可以在必要时调用 getEarlyBeanReference 提前返回代理对象如果只存原始对象循环依赖时注入的就不是最终代理对象语义上会出现不一致。prototype Bean 为什么不能解决循环依赖原型 Bean 每次获取都创建新对象容器没有缓存早期引用也分不清该注入哪一次创建出来的实例因此无法破解循环依赖。Async、Transactional 失效的原因本质上是代理对象与原始对象不一致以及 BeanPostProcessor 执行时机导致
返回列表