Spring源码:无参构造器实例化的完整链路解析
发布时间:2026/10/6 9:45:55 作者:尧图编辑部 阅读量:1,286

Spring源码中createBean()是AbstractAutowireCapableBeanFactory创建Bean的核心方法而它内部首先要解决的一件事这个Bean的实例该由哪个构造器new出来。无参构造器也就是默认构造器是最常见的场景也是理解整个构造器解析体系的地基。这篇文章会和你一起走一遍无参构造器场景下createBean寻找构造器的完整过程顺带把ConstructorResolver、SmartInstantiationAwareBeanPostProcessor、CglibSubclassingInstantiationStrategy这些名字串起来。适合正在啃Spring源码、搞不清doCreateBean和createBeanInstance之间关系或者想搞懂为什么有时候构造器选择会“不按套路出牌”的同学参考。1. 从 getBean 到 createBean构造器选择的入口1.1 为什么需要“寻找构造器”JVM创建一个Java对象最终都要落到某个构造器上。Spring作为IoC容器不能像普通代码那样直接new一个对象它必须根据BeanDefinition里的元信息、构造器参数、注解配置甚至BeanPostProcessor给出的建议决定到底调用哪个构造器。这个决定过程就是“寻找构造器”。很多人以为Spring实例化Bean就是“调无参构造器”这是对小型项目的直觉但在复杂场景下明显不够用。当一个Bean定义了多个构造器或某个构造器标了Autowired或配置了constructor-argSpring就必须有一套明确的决策规则。否则依赖注入就成了“乱点鸳鸯谱”。从设计角度看构造器选择是Bean实例化策略中最核心的分支逻辑。它直接决定了后续属性填充、初始化回调能不能顺利进行。理解它不光是应付源码面试更是排查“Bean实例化失败”“多个构造器报错”这类问题的基本功。1.2 createBean 的调用链doCreateBean 之前发生了什么先从入口看起。AbstractAutowireCapableBeanFactory#createBean是getBean之后的真正创建方法它会做三件大事解析并处理BeanClass调用prepareMethodOverrides()处理lookup-method和replaced-method的覆盖标记执行resolveBeforeInstantiation()给BeanPostProcessor一个“提前介入”的机会。这里的resolveBeforeInstantiation很容易被忽略其实它和AOP有着直接关系。如果你在BeanPostProcessor的postProcessBeforeInstantiation方法里直接返回了一个代理对象那么Spring会直接把这个对象作为最终bean返回后面doCreateBean里的构造器解析流程根本不会执行。这也是“无参构造器寻找”被跳过的第一种情况。如果一切正常进入doCreateBean。doCreateBean的第一步就是从factoryBeanInstanceCache里尝试取缓存的包装对象取不到就调用createBeanInstance(beanName, mbd, args)。createBeanInstance就是寻找构造器的现场。调用链可以简单记为getBean - doGetBean - createBean - doCreateBean - createBeanInstance建议你第一次看源码时只盯这条线别被后面一堆回调带偏。2. 无参构造器的判定逻辑ConstructorResolver 的抉择2.1 createBeanInstance 里的关键分支createBeanInstance的源码不长但每一步都有讲究。核心代码可以抽象成下面几步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) { return autowireConstructor(beanName, mbd, ctors, null); } return instantiateBean(beanName, mbd);这里的顺序很有讲究Supplier优先级最高工厂方法第二然后才轮到构造器解析。如果Supplier存在哪怕你写了工厂方法和构造器也用它。对于大多数普通BeanSupplier和工厂方法都是null真正决定走哪条路的是后面两段判断。另外createBeanInstance在正式解析之前还有一段针对构造器的缓存判断。如果args为null框架会加锁检查RootBeanDefinition的resolvedConstructorOrFactoryMethod是否已经非空。已经解析过就直接复用结果如果之前判定需要构造器参数则走autowireConstructor否则走instantiateBean。这也是为什么第二次创建相同Definition时速度会明显更快。第一段判断里determineConstructorsFromBeanPostProcessors会遍历所有SmartInstantiationAwareBeanPostProcessor让它们返回“候选构造器”。如果任何一个PostProcessor返回了候选数组就说明开发环境里已经明确指定了要用的构造器此时直接走autowireConstructor去解析参数并实例化。第二段判断是说如果autowireMode显式配成了构造器自动装配即使没有候选构造器也会强制走autowireConstructor。只有这两段都不满足才落到方法最下面的instantiateBean——也就是无参构造器场景。2.2 无参构造器为什么是“兜底”无参构造器是所有Java类默认携带的构造器。只要类里没有显式定义其他构造器编译器就会生成一个无参构造器。Spring把这个默认构造器当成“兜底方案”再合适不过绝大多数POJO、配置类、组件类都没有特殊构造器需求直接反射调用无参构造器就能得到实例成本最低逻辑最简单。用类比来说出门前你如果没有指定“要坐奔驰”“需要带两个箱子”那默认就是腿着走。Spring也一样没有额外信息时无参构造器就是最省事的一条路。但别忘了“腿着走”并不等于“没有选择”而是默认选择的结果。一旦有候选构造器出现局面就会变复杂。另一点值得注意Spring在instantiateBean里不会检查构造器是否public。它会调用getDeclaredConstructor()拿到构造器再通过ReflectionUtils.makeAccessible()强制放开访问权限。所以即使你的无参构造器是privateSpring也能创建实例。这跟普通new严格区分开是反射带来的能力。2.3 源码走读determineCandidateConstructors 为什么返回 null无参构造器场景成立的标志是determineConstructorsFromBeanPostProcessors返回null。这个方法内部长这样protected Constructor?[] determineConstructorsFromBeanPostProcessors(Nullable Class? beanClass, String beanName) { if (beanClass ! null) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor sbp (SmartInstantiationAwareBeanPostProcessor) bp; Constructor?[] ctors sbp.determineCandidateConstructors(beanClass, beanName); if (ctors ! null) { return ctors; } } } } return null; }真正会返回候选构造器的PostProcessor最常见的是AutowiredAnnotationBeanPostProcessor。它会扫描类中的构造器如果某个构造器标了Autowired就会作为一个候选。还有一种情况也会被它挑出来类里只有一个构造器并且这个构造器不是无参构造器。这种情况下即使没标Autowired它也会认为“这个Bean默认应该用带参构造器”返回候选列表。如果你的类只有无参构造器没有其他构造器也没有任何Autowired标记那么AutowiredAnnotationBeanPostProcessor在determineCandidateConstructors里扫了一圈发现没有值得推荐的构造器就会返回null。此时createBeanInstance自然进入instantiateBean。这里有个容易踩坑的点如果类里同时有无参构造器和带参构造器但带参构造器没有标Autowired结果如何这取决于多个PostProcessor的组合和AutowiredAnnotationBeanPostProcessor内部的“单一构造器才自动选中”策略。多个构造器存在时它不会默认把某一个当作唯一候选。这类问题我们放到第4节和第5节后面展开属于有参构造器系列的内容。3. 实例化流程从构造器到 BeanWrapper3.1 实例化策略CglibSubclassingInstantiationStrategy 的“伪装”走到instantiateBean这一步Spring会调用实例化策略。实例化策略接口是InstantiationStrategy默认实现是CglibSubclassingInstantiationStrategy。很多人看到Cglib三个字就以为Spring所有Bean都用CGLIB创建这是误解。CglibSubclassingInstantiationStrategy继承自SimpleInstantiationStrategy它重写了instantiate方法Override public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) { if (bd.getMethodOverrides().isEmpty()) { return super.instantiate(bd, beanName, owner); } return instantiateWithMethodInjection(bd, beanName, owner); }只有当RootBeanDefinition里的methodOverrides不为空也就是配置了lookup-method或replaced-method时默认策略才会换成CGLIB动态生成子类。而绝大多数Bean的methodOverrides都是空的所以最终会回到父类SimpleInstantiationStrategy的纯反射逻辑。换句话说CGLIB在这里是个“备胎策略”它服务的是方法注入这种特殊需求而不是常规构造器实例化。看到这个名字时应该联想的是“lookup-method”而不是AOP代理。AOP代理的生成由ProxyFactory完成那是另一条线后文会再提一句。3.2 无参构造器场景下的 Bean 创建全过程在SimpleInstantiationStrategy#instantiate中如果methodOverrides为空会执行类似这样的逻辑Constructor? constructorToUse bd.getResolvedConstructor(); if (constructorToUse null) { constructorToUse clazz.getDeclaredConstructor(); bd.setResolvedConstructor(constructorToUse); } return BeanUtils.instantiateClass(constructorToUse);这里有两个小细节值得学习。第一是缓存。Spring会先把解析出来的构造器存到bd.setResolvedConstructor里下次创建同一个BeanDefinition时就不用再走一遍getDeclaredConstructor。对于单例Bean这个缓存意义不大因为单例Bean大部分只创建一次但对prototype Bean来说每次getBean都会重新创建缓存能省下一堆反射查找开销。第二是getDeclaredConstructor与getConstructor的区别。getConstructor()只能拿public构造器getDeclaredConstructor()能拿到类的所有构造器包括private。Spring用后者再配合BeanUtils内部对非public构造器的访问权限处理才能做到“私有构造器也能实例化”。这对一些工具类、框架内部类很关键。拿到构造器后BeanUtils.instantiateClass会new一个实例出来。这个实例还是“裸的”没有注入任何属性。紧接着instantiateBean方法会把实例包装成BeanWrapperImpl完成类型转换器等基础配置返回给doCreateBean。doCreateBean再继续做属性填充populateBean和初始化initializeBean。所以无参构造器场景下createBeanInstance的职责是找到默认构造器、创建裸对象、包一层BeanWrapper。真正的业务依赖注入是在后面几步完成的。4. 常见问题与排查技巧实录4.1 为什么有时候无参构造器没有被选中这是我在论坛里见过最多的问题。简单总结原因基本集中在三类某个构造器被Autowired标记了。AutowiredAnnotationBeanPostProcessor会把它作为候选Spring直接走autowireConstructor无参构造器被跳过。配置了构造函数自动装配。比如XML里的autowireconstructor或者通过CustomAutowireConfigurer设置导致mbd.getResolvedAutowireMode()变成AUTOWIRE_CONSTRUCTORSpring强制尝试用构造器注入。自己写了SmartInstantiationAwareBeanPostProcessor并且在determineCandidateConstructors里返回了非null数组。这种情况相对少见但一旦出现优先级极高。排查时最直接的方式是在createBeanInstance的这两行打断点打印两个值Constructor?[] ctors determineConstructorsFromBeanPostProcessors(beanClass, beanName); int autowireMode mbd.getResolvedAutowireMode();只要看到ctors不为null或者autowireMode大于0无参构造器被跳过就是正常的。4.2 如何通过断点验证无参构造器选择过程要验证“无参构造器场景”可以准备一个最小Demo。定义一个类Component public class DemoBean { private String name; public DemoBean() { this.name default; } public String getName() { return name; } }然后用AnnotationConfigApplicationContext启动AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(com.example); DemoBean bean ctx.getBean(DemoBean.class); System.out.println(bean.getName());在AbstractAutowireCapableBeanFactory#createBeanInstance方法第一行打断点观察程序进入后单步执行到determineConstructorsFromBeanPostProcessors(beanClass, beanName)这一步查看返回的ctors应该是null。再继续往下会看到autowireMode为0最终进入instantiateBean方法。接着在SimpleInstantiationStrategy#instantiate方法打断点就可以看到getDeclaredConstructor被调用最后走BeanUtils.instantiateClass。如果你想更“现场”一点可以启动时配置debug日志logging.level.org.springframework.beans.factory.supportdebug日志会输出一些BeanDefinition和实例化相关信息但要精确到构造器选择过程断点还是最直观的。4.3 多个构造器时的决定规则为后续文章埋个伏笔如果类里同时有无参构造器和带参构造器且没有Autowired也没有constructor-argSpring会怎么选这个问题的完整答案在ConstructorResolver#autowireConstructor里。简单透露一个方向Spring会先收集所有“合适”的构造器包括无参构造器。然后根据BeanDefinition里的构造器参数或者容器中已有的Bean类型逐一尝试匹配可用的参数。多个候选之间会有优先级判断优先选择能够完全匹配参数的构造器。如果实在匹配不上可能抛出异常。这个规则比无参构造器场景复杂得多涉及参数解析器、类型转换、循环依赖下的提前实例化。我们放在下一篇专门拆解。这里埋个伏笔无参构造器并不是所有时候都能“躺赢”只要候选列表出现它也可能只是备选之一。5. 经验总结与影响范围5.1 理解构造器选择对 Spring 扩展开发的意义很多人读源码只是为了面试但构造器选择这件事在真实开发里能派上用场。你在写自定义SmartInstantiationAwareBeanPostProcessor时可以通过determineCandidateConstructors直接干预Spring的构造器选择。比如在某些框架里希望“所有以特定后缀命名的类都用带参构造器”就可以在这里返回一个指定数组Spring会乖乖执行。这种扩展点一旦用起来比在XML里堆constructor-arg灵活得多。再比如AOP。Spring AOP在大多数情况下是先通过createBeanInstance把原始Bean创建出来再在初始化后处理阶段通过ProxyFactory生成代理对象。很多人一看到CglibSubclassingInstantiationStrategy就以为是AOP在作怪其实两者不在一个阶段。代理生成影响的是最终返回给你的对象而构造器选择影响的是最原始的那个目标对象。分清这两层排查代理相关问题时才不会头脑混乱。5.2 从源码里学到的缓存与反射技巧我建议你带着“为什么这么设计”的眼光去看这些代码能学到不少可复用的经验。比如构造器缓存。SimpleInstantiationStrategy把解析出的构造器存进RootBeanDefinition避免了重复反射。虽然这只是一个很小的优化但如果你在做批量创建对象的框架可以模仿同样的思路把Class解析结果缓存起来别每次都new一个Constructor。再比如非public构造器处理。Spring反射调用私有构造器时会主动设置setAccessible(true)这在普通业务代码里可能不受待见但在框架层是完全可接受的。如果你的项目里需要兼容外部类或者想限制用户直接new但允许容器创建这个思路值得一试。5.3 系列内容的预告这篇文章只讲了无参构造器场景算是把“构造器寻找”的最小Case跑通了。说实话源码里真正烧脑的地方在于有参构造器的参数匹配以及多个候选构造器存在时的“择优录取”。下一篇我会继续拆ConstructorResolver的autowireConstructor方法把参数解析、异常兜底、循环依赖下的构造器选择全部展开。你在调试无参构造器时如果遇到奇怪问题先把mbd.getResolvedAutowireMode()和ctors打出来九成问题都出在这两个值上。这是我实际调试中回报率最高的一步。先把这条诊断路径练熟再进入有参构造器的复杂世界你会走得稳很多。