getmergedlocalbeandefinition 是合并入口,每次调用均现场执行递归合并,非读取缓存;mergedbeandefinitions 仅为弱缓存且易失效;合并发生在 getbean、getbeannamesfortype、preinstantiatesingletons、docreatebean 等多节点;childbeandefinition 需继承父定义并最终生成 rootbeandefinition。

getMergedLocalBeanDefinition 是合并入口,不是缓存读取
调用 getBean("userService") 时,Spring 并不会直接从某个“合并缓存”里拿现成的 RootBeanDefinition。它每次都会调用 getMergedLocalBeanDefinition(beanName),现场执行合并逻辑:先取子定义(如 ChildBeanDefinition),再递归取父定义(parent="baseService" 指向的那个),最后把父的属性逐个复制、再用子的同名属性覆盖。
常见误解是认为合并只做一次、结果被长期缓存。实际上:mergedBeanDefinitions 这个 Map 只是弱缓存(key 是 beanName,value 是合并后结果),但缓存失效很频繁——比如你调用 registerBeanDefinition("c", newChildBeanDefinition("p")) 覆盖了原定义,下一次 getMergedLocalBeanDefinition("c") 就会重新合并。
- 父定义必须已注册,否则抛
BeanDefinitionStoreException: Parent name 'xxx' is invalid - 抽象父定义(
abstract="true")不能被直接 getBean,但可以被继承;若子定义没覆盖必要属性(如class),合并后仍缺 class,实例化时抛BeanInstantiationException - 合并不检查 Java 类型兼容性——
parent指向一个BaseService,子定义设class="UserService",Spring 不验证UserService是否继承BaseService
merge 发生在多个关键节点,不止 getBean 时
合并不是只在 getBean() 时触发。它在 Spring 生命周期多个环节被主动拉起:
-
getBeanNamesForType(Class> type)内部调用doGetBeanNamesForType(),会先对所有beanDefinitionNames执行一遍getMergedLocalBeanDefinition(),确保按合并后的类型匹配(否则parent="baseService"的子 Bean 可能被漏掉) -
preInstantiateSingletons()启动预实例化前,遍历所有单例 beanName,对每个都调一次getMergedLocalBeanDefinition() -
doCreateBean()开始实例化前,再次调用该方法获取最终RootBeanDefinition,此时还可能被MergedBeanDefinitionPostProcessor修改(如AutowiredAnnotationBeanPostProcessor会往mbd里塞autowireCandidate标记)
这意味着:你改了一个父 Bean 的属性,所有继承它的子 Bean 在下次触发上述任一操作时,都会拿到新合并的结果——不是热更新,但也不是静态快照。
ChildBeanDefinition 和 RootBeanDefinition 的行为差异
ChildBeanDefinition 本身不包含完整信息,它只存自己声明的属性和 parentName;而 RootBeanDefinition 是合并终点,可直接用于实例化。注意几个关键点:
-
ChildBeanDefinition的scope、lazyInit、abstract等布尔/枚举属性,若未显式设置,则继承父定义值;但beanClass、constructorArgumentValues、propertyValues这三类必须由子定义提供或继承自父定义——没有默认值 - 父定义是
RootBeanDefinition,子定义是ChildBeanDefinition,合并结果一定是RootBeanDefinition;但如果父定义也是ChildBeanDefinition(即多级继承),合并会递归向上,直到触达顶层RootBeanDefinition - 子定义中
propertyValues若含同名 key(如都设了version),子值无条件覆盖父值;但若父有version=1.0,子没设version,则保留 1.0
调试合并过程最有效的断点位置
想亲眼看到合并怎么一步步发生,不要埋点在 getBean(),直接打在:
-
AbstractBeanFactory.getMergedLocalBeanDefinition(String)—— 进入合并主逻辑 -
AbstractBeanDefinition.mergeAttributes(BeanDefinition)—— 真正执行属性复制与覆盖的私有方法,能看到source(父)、target(子)如何逐字段处理 -
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod()—— 如果你用@Bean方法定义父子关系(如@Bean @Scope("prototype") BaseService baseService()),这里会生成带parentName的ChildBeanDefinition
合并逻辑本身不复杂,但嵌套调用深、分支多。真正容易出问题的不是合并算法,而是父定义缺失、子定义漏写 class、或在 MergedBeanDefinitionPostProcessor 中误改了 mbd 的不可变字段(如 mbd.setBeanClass(null) 导致后续空指针)——这些错误往往在实例化阶段才暴露,回溯时得从合并入口开始查。










