
很多人背Spring Bean生命周期张口就能说出“实例化、属性填充、初始化、销毁”四板斧但真被问到“实例化这一步到底在哪里触发Spring是拿什么信息去new对象的如果没有无参构造器这Bean还能不能建出来”的时候能讲清楚的人其实不多。我最早啃源码时也是直奔doCreateBean()结果翻了半天一脸懵后来才意识到问题出在把“实例化”当成了一个孤立动作。实例化只是创建流程里的一环前面有BeanDefinition的加载与合并后面紧跟着属性填充和初始化。你想真正理解实例化先得搞清楚Spring凭什么能new出一个对象——不是靠猜靠的是图纸。1. 实例化不是newBeanDefinition才是那张施工图纸BeanDefinition是什么你可以把它理解成一张施工图纸。XML里的bean标签、Bean注解方法、Component扫描出来的类最终都会被解析成一个个BeanDefinition实例。这张图纸上记录了Bean的类名、作用域、构造参数、属性值、初始化方法名、是否懒加载、是否抽象等元数据。实例化动作的唯一输入就是这玩意。去看AbstractAutowireCapableBeanFactory#createBeanInstance()的源码你会发现Spring创建实例时第一步就是拿RootBeanDefinition。为什么要强调“Root”因为Spring在处理Bean定义时存在合并机制如果你通过parent指定了父BeanDefinition子Bean会继承父级的属性、构造参数、作用域等信息合并后的结果才是一份完整可用的图纸。这步合并操作是很多人忽略的坑。我记得有一次排查线上配置不生效的问题子Bean明明设置了init-method运行起来却一直走父类的初始化逻辑。查了半天发现是继承配置时父级把initMethodName给覆盖了准确说是合并逻辑里子级未显式配置时沿用了父级值。所以聊实例化流程时脑子里要永远绷着一根弦任何一次创建动作都必须经历“图纸完善 → 正式开工”两个阶段图纸没画好后面全是坑。BeanDefinition里跟实例化强相关的字段主要有这几个字段作用影响实例化的方式beanClassName指定Bean的类全限定名决定了实例化时加载哪个类constructorArgumentValues构造器参数决定了选择哪个构造器、传什么值factoryMethodName工厂方法名如果设置了将走静态工厂或FactoryBean路径autowireMode自动装配模式影响构造器参数的自动解析abstract是否抽象抽象Bean定义不会实例化只当模板使用这里插一句关键经验beanClassName并不总是必需的。如果你配置的是工厂Bean方式className甚至可以写工厂类本身真正创建的对象类型由工厂方法返回值决定。很多人在这里死记硬背“Bean必须写class”遇到FactoryBean就懵了。2. 从getBean到createBean实例化前的入口与钩子实例化不是无缘无故发生的一切入口都是BeanFactory#getBean()。但getBean()是个门面方法真正的逻辑在AbstractBeanFactory#doGetBean()里。这一步值得展开讲因为坑都藏在调用链上。doGetBean()首先检查单例缓存里有没有现成的Bean——这里涉及“三级缓存”的第一级如果有直接返回实例化流程根本不走。只有缓存里没有才进入创建流程。创建时Spring会通过createBean()这个模板方法继续往下走而createBean()是AbstractAutowireCapableBeanFactory里的核心方法它的执行顺序很关键获取RootBeanDefinition检查beanClass是否可实例化。如果存在InstantiationAwareBeanPostProcessor调用postProcessBeforeInstantiation()——注意如果这个方法返回了非空对象实例化流程直接结束后面什么都不用干了。走doCreateBean()真正创建实例。我最初看这段源码时有个迷惑点为什么实例化之前要调用postProcessBeforeInstantiation它存在的意义是什么答案是为了支持AOP代理、动态代理生成等场景——比如AbstractAutoProxyCreator就是在这里通过提前判断是否需要代理来决定要不要绕过正常实例化流程。如果这个后置处理器直接返回了一个代理对象那Spring就拿着这个代理对象往下走属性填充不对它直接跳过属性填充了后续的初始化流程也是基于这个代理对象走的。所以这个钩子非常强大但也非常容易误用。再往下走就是resolveBeforeInstantiation()的孪生兄弟postProcessAfterInstantiation()它在实例化完成、属性填充之前被调用返回boolean。返回false的话Spring会跳过属性填充阶段——这也是一个容易被忽视的“暗门”。我记得有同事自定义了一个InstantiationAwareBeanPostProcessor因为返回值写错导致所有Bean属性都是null排查了很久。所以说实例化不是一锤子买卖它在入口处就埋了好几个钩子。不明白这些钩子很多诡异的“为什么我的Bean不是new出来的”问题根本无从下手。3. 三种实例化路径构造器、静态工厂方法、FactoryBean可不是选择题走到createBeanInstance()这一步Spring会根据RootBeanDefinition里的信息分派实例化策略。这里就是整个流程的核心战场一共三条路构造器实例化、静态工厂方法实例化、实例工厂方法实例化FactoryBean原理。3.1 构造器实例化最常见也是最灵活的路径先看常规逻辑如果BeanDefinition里没有factoryMethodNameSpring就默认走构造器路径。这里有几种情况默认无参构造器beanClass.getDeclaredConstructor()直接new。唯一构造器且带参Spring会尝试通过constructorArgumentValues解析参数或者根据Autowired注解自动注入。多个构造器交给SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors()去选拔选不出唯一就抛NoSuchMethodException。重点说下多构造器情况。有人觉得“我写了两个构造器Spring应该都知道吧”——还真不一定。Spring的自动装配模式下如果存在多个构造器且没有标注Autowired默认会优先选无参构造器如果没有无参构造器那就看参数最多的构造器不对准确逻辑是匹配constructorArgumentValues里显式配置的参数。实际开发中我强烈建议多构造器场景要么只保留一个Autowired标注的构造器要么在配置里明确指定constructor-arg。不要指望Spring预判你的心思。参数解析也值得多说一句。构造器参数值可能来自配置文件的占位符、BeanDefinition里的显式值、或者容器里其他Bean。解析顺序是先处理后一个具体由ConstructorResolver.resolveConstructorArguments()负责它会根据constructorArgumentValues里每个值的类型和索引去匹配。这里有个隐蔽问题参数类型不精确匹配时Spring会尝试类型转换转换失败才报错。所以“类型对不对”往往不是第一现场转换器反而是排查重点。3.2 静态工厂方法被低估的创建方式当BeanDefinition配置了factory-method时Spring会调用指定类的静态方法来创建实例而不是直接new。典型场景在早期Spring代码里很常见比如java.util.concurrent.Executors、DateFormat这些工具类经常通过静态工厂暴露。public class MyBean { private MyBean() {} public static MyBean createInstance(String name) { MyBean bean new MyBean(); // 这里可以做额外初始化 return bean; } }对应的XML配置长这样bean idmyBean classcom.example.MyBean factory-methodcreateInstance constructor-arg namename valuedemo/ /bean你注意这里constructor-arg传给的不是构造器而是工厂方法的参数。Spring会拿这些参数去匹配createInstance(String)方法签名。如果工厂方法是带泛型或者重载的解析逻辑会比普通构造器更复杂。另外一个容易踩的坑是静态工厂方法的访问权限——方法必须是static的而且Spring默认会去找public方法。如果你把工厂方法写成private static启动时会报NoSuchMethodException但这个报错信息比较绕经常让人误以为类本身有问题。3.3 FactoryBean另一种“实例化引擎”FactoryBean是Spring框架里一个非常特殊的存在很多人常把它和BeanFactory搞混。FactoryBean本身是一个Bean但它生产的不是自己而是getObject()方法的返回值。这个机制的经典应用就是MyBatis的MapperFactoryBean、Ribbon的RibbonLoadBalancerClient等等。Component public class MyFactoryBean implements FactoryBeanMyService { Override public MyService getObject() { return new MyService(); } Override public Class? getObjectType() { return MyService.class; } Override public boolean isSingleton() { return true; } }当你在代码里Autowired MyService时Spring会发现MyFactoryBean这个Bean调用它的getObject()拿到真正的MyService。但注意如果你想注入的是MyFactoryBean本身得在限定时加一个前缀。从实例化流程角度看FactoryBean路径上的实例化对象是MyFactoryBean本身——它先被当作普通Bean创建出来然后容器再去调用getObject()。所以FactoryBean内部对象的创建并不受Spring Bean生命周期全程管理比如PostConstruct、InitializingBean这些回调Spring只在FactoryBean本身上执行getObject()返回的对象如果是普通new出来的它就没有Spring管理的那一套初始化流程。这个点特别容易引发事故——我在一个项目里见过有人把Autowired写进FactoryBean生产的对象里结果注入的是null因为那个对象压根没走Bean的创建流程。三种路径的选择关系我用一个表总结下方便对照路径配置关键点实例化产物适用场景构造器无factory-methodbeanClass的实例绝大多数普通Bean静态工厂有factory-method方法是static工厂方法返回值工具类、不可直接new的类FactoryBeanbeanClass指向FactoryBean实现类getObject()的返回值代理生成、动态对象、框架集成4. 构造器选拔赛determineCandidateConstructors背后的规则实例化路径确定之后真正的硬仗是构造器的选择。Spring不是乱挑构造器它有一套完整的候选者选拔机制核心入口是SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors()。这个接口的默认实现是AutowiredAnnotationBeanPostProcessor它负责处理带Autowired的构造器、以及相关注解标注的构造器。选拔规则大致如下如果类里存在多个构造器但只有一个标注了Autowired那就选这个。如果存在多个构造器都标注了Autowired且requiredtrue——直接报错。如果没有任何构造器标注Autowired且存在唯一的有参构造器Spring会尝试把它当作候选构造器前提是它能解析到所有参数。如果配置了constructorArgumentValues比如XML里写了constructor-argSpring会优先根据参数值去匹配构造器。我实战里见过最妖的场景一个类有两个构造器一个无参一个带参配置里什么都没写Spring启动时直接用了无参构造器创建了Bean。后来加了构造器参数自动装配需求发现带参构造器死活没被选中——原因就是AutowiredAnnotationBeanPostProcessor在扫描时只认Autowired注解。没有注解它就是不会主动去“猜”你要用哪个有参构造器除非你显式配置了constructor-arg。所以如果你遇到“明明写了有参构造器Spring却用无参的”这种问题先别怀疑人生去看看两件事构造器有没有标AutowiredBeanDefinition里有没有constructorArgumentValues这两点占一个有参构造器才有机会被选中。再说参数名解析。构造器参数要能自动注入Spring必须知道参数名。-parameters编译参数开启时可以用反射拿到真实参数名没开启时会退回到LocalVariableTableParameterNameDiscoverer这个类依赖编译时生成的调试信息。如果调试信息也关了参数名就变成arg0、arg1解析必挂。要让Qualifier或者XML里的constructor-arg name生效编译时一定要加-parameters。5. 创建完就“提前曝光”循环依赖为什么能在这里被解决实例化完成并不代表Bean已经可用紧接着是属性填充和初始化。但在这两者之间有一个极重要的环节addSingletonFactory()方法注册的“三级缓存”提前暴露。这个环节是整个Spring循环依赖解决方案的基石。简单说Spring在实例化完成、属性填充之前会把一个ObjectFactory放到三级缓存里。这个ObjectFactory的getObject()返回的是一个earlySingletonReference——也就是一个早期引用。为什么叫“早期”因为此时Bean的属性还没填充、初始化还没执行它只能说是一个“半成品对象”。循环依赖的经典场景是A依赖B、B依赖A。创建A时A实例化完成提前把ObjectFactory暴露到三级缓存接着A开始填充属性发现自己需要B于是去创建BB创建时填充属性发现自己需要A这时从三级缓存里找到A的ObjectFactory调用getObject()拿到A的早期引用注入给BB创建完成后A再继续从容器里拿到B的实例填充进来最终完成整个创建流程。有人问为什么要三级缓存而不是实例化完直接放到二级缓存关键在于getEarlyBeanReference()这个方法——它可能被SmartInstantiationAwareBeanPostProcessor拦截被包装成代理对象。比如AOP代理的场景下早期暴露出去的必须是代理对象而不是原始对象。如果直接放原始对象到二级缓存后面AOP包装的不是同一个对象循环依赖就断了。所以三级缓存里的ObjectFactory要承担“按需生成代理或原始引用”的任务这就是它存在的意义。不过要泼一盆冷水循环依赖只在单例singletonBean的setter注入场景下能解决。构造器注入的循环依赖是无解的因为构造器注入要求B在A完成实例化之前就要存在而A此时还没暴露任何东西。很多人在这块栽过跟头把Autowired放在构造器上然后A构造器里需要B、B构造器里需要ASpring直接抛BeanCurrentlyInCreationException。所以设计上如果真的离不开循环依赖尽量用setter/字段注入但更推荐直接用构造器注入配合重构——循环依赖本身就是设计坏味道能解就别留。6. 实例化前后的翻车现场几个典型故障的排查链路这部分把我在项目里实际碰到的、以及帮别人排查过的实例化相关故障做个记录。每一个案例背后都对应着某个具体的源码细节希望你能在遇到类似问题时少走弯路。6.1 抽象类被实例化配置BeanDefinition时踩的坑有个团队把业务基类做成了抽象类里面已经实现了大部分通用逻辑然后希望通过配置XML让Spring去实例化它。启动时报错Cannot instantiate abstract class。排查链路其实很简单isAbstract()是BeanDefinition里的一个标志位Spring拿到BeanClassName之后会调用ClassUtils.isAbstract()去判断。抽象类、接口都不能被实例化。但这个项目里的根因是配置的人不知道抽象基类不应该配成Bean而应该作为父Bean定义abstracttrue让子Bean去继承配置。这是很规范的Spring用法父Bean定义只做配置模板不实例化。6.2NoSuchMethodException构造器访问权限与参数不匹配另一个高频场景是启动报错java.lang.NoSuchMethodException: com.example.Service.init()。看到这个异常很多人都以为是Spring找不到无参构造器实际上分两种一是类里压根没写无参构造器但有有参构造器二是无参构造器是private的。Spring在实例化时用getDeclaredConstructor()去找构造器拿到之后会调用makeAccessible()来突破访问限制。也就是说private构造器其实是可以用的。那为什么还会报NoSuchMethodException多半是因为constructorArgumentValues里配置的参数个数和实际构造器参数个数不匹配导致ConstructorResolver在匹配时找不到合适的构造器。这类问题的排查思路不是看异常栈而是去看配置的constructor-arg数量和类型。6.3 FactoryBean的getObject()里抛异常实例化成功了一半第三种情况特别有意思Bean实例化成功了属性也填充完了但通过Autowired注入时发现对象是null。原因前面提到过——FactoryBean生产出来的对象不走Spring生命周期管理。具体到这个案例是getObject()方法内部自己new了一个对象然后这个对象里用了Resource注解想注入另一个Bean结果自然是null。解决方式有两种要么在FactoryBean实现类里通过ApplicationContextAware把需要的Bean手动取出来传给生产对象要么干脆放弃FactoryBean改用Bean方法返回那个对象——Bean方法的返回值Spring还是会走完整生命周期管理的前提是没有在后续被代理替换。6.4BeanCurrentlyInCreationException构造器循环依赖的识别最后这个异常比较典型BeanCurrentlyInCreationException: Error creating bean with name a: Requested bean is currently in creation: Is there an unresolvable circular reference?看到这个直接检查构造器注入。前面说过三级缓存解决的是属性注入和Autowired字段注入的循环依赖因为属性填充是在实例化之后、可以提前暴露早期引用构造器注入则发生在实例化进行时Spring还没有把这个Bean暴露出去。排查的时候打印一下调用栈基本能看出是哪几个Bean在互相引用构造器。收个尾把实例化当入口不要当终点Spring Bean的实例化看着简单但真钻进去里面涉及图纸解析、工厂策略、后置处理器、构造器选拔、循环依赖处理好几个环环相扣的设计。我个人看源码这么多年最大的体会是不要试图一次性把所有细节背下来而是带着问题去看。比如你遇到FactoryBean的问题就顺着getObjectForBean去看遇到构造器报错就顺着determineCandidateConstructors去看。实例化这个入口其实串联起了一整套扩展点——BeanPostProcessor、InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor都在这个阶段发挥作用。如果想进一步练习建议把ClassPathXmlApplicationContext的启动流程撸一遍断点打在createBeanInstance()上跟着一个最简单的Bean走一个完整的创建链路比背十遍生命周期都管用。最后再分享一个我自己的小习惯拿到不熟悉的框架代码之前会先把“谁调用我、我调用谁”这两条链路画清楚再来追细节这样不会在源码海洋里迷路。