bigywater.tech
Spring · 170

BeanRegistrar 实测:用代码循环注册 Bean,同名冲突与 @ConditionalOnMissingBean 的表现

Framework 7 新增的 BeanRegistrar 怎么用代码批量注册 Bean?Spec 能设哪些选项,同名冲突时报什么,@ConditionalOnMissingBean 能不能看到它注册的 Bean?这篇在 Spring Boot 4.0.8 上逐项运行。

3 个
一个 register 方法里按 shards=3 循环注册出的 Bean 数,按类型取抛 NoUniqueBeanDefinitionException
defined in null
同名冲突时的异常信息里,BeanRegistrar 注册的定义没有位置信息
2 个
@ConditionalOnMissingBean(Shard.class) 在 BeanRegistrar 已注册一个 Shard 的情况下仍然创建了第二个

循环注册

实测 · 本机执行输出
    构造 Shard(0)
    构造 Shard(1)
    构造 Shard(2)
  容器里 Shard 类型的 Bean 名:[shard0, shard1, shard2]
    getBean("shard0") 两次取到 Shard(0),同一个对象
    getBean("shard1") 两次取到 Shard(1),同一个对象
    getBean("shard2") 两次取到 Shard(2),同一个对象
    按类型取 getBean(Shard.class) 异常 NoUniqueBeanDefinitionException:No qualifying bean of type 'demo.Shard' available: expected single matching bean but found 3: shard0,shard1,shard2

读 Environment 里的 shards=3,循环注册三个 Bean。

Spec 选项

实测 · 本机执行输出
    构造 Shard(30)
  容器里 Shard 类型的 Bean 名:[sEager, sLazy, sProto]
    getBean("sEager") 两次取到 Shard(30),同一个对象
    构造 Shard(20)
    getBean("sLazy") 两次取到 Shard(20),同一个对象
    构造 Shard(10)
    构造 Shard(10)
    getBean("sProto") 两次取到 Shard(10),不同对象
    按类型取 getBean(Shard.class) 异常 NoUniqueBeanDefinitionException:No qualifying bean of type 'demo.Shard' available: expected single matching bean but found 3: sProto,sLazy,sEager

默认单例启动时构造,lazyInit 第一次取才构造,prototype 每次新建。

primary 与 fallback

实测 · 本机执行输出
    构造 Shard(1)
    构造 Shard(2)
  容器里 Shard 类型的 Bean 名:[a, b]
    getBean("a") 两次取到 Shard(1),同一个对象
    getBean("b") 两次取到 Shard(2),同一个对象
    按类型取 getBean(Shard.class) = Shard(2)
  -- 换成 fallback 的注册方式
    构造 Shard(1)
    构造 Shard(2)
  容器里 Shard 类型的 Bean 名:[x, y]
    getBean("x") 两次取到 Shard(1),同一个对象
    getBean("y") 两次取到 Shard(2),同一个对象
    按类型取 getBean(Shard.class) = Shard(2)

两种标记都能在多个候选里选出确定的一个。

registerBean(Class)

实测 · 本机执行输出
    registerBean(Auto.class) 返回的 Bean 名:demo.Auto#0
    构造 Auto,注入的 Dep=Dep
  容器里 Shard 类型的 Bean 名:[]
    按类型取 getBean(Shard.class) 异常 NoSuchBeanDefinitionException:No qualifying bean of type 'demo.Shard' available

不给名字时名字自动生成,构造器参数自动注入。

不激活 cloud

实测 · 本机执行输出
  容器里 Shard 类型的 Bean 名:[]
    Store 类型的 Bean:LocalStore
    按类型取 getBean(Shard.class) 异常 NoSuchBeanDefinitionException:No qualifying bean of type 'demo.Shard' available

按 profile 注册 LocalStore。

激活 cloud

实测 · 本机执行输出
  容器里 Shard 类型的 Bean 名:[]
    Store 类型的 Bean:CloudStore
    按类型取 getBean(Shard.class) 异常 NoSuchBeanDefinitionException:No qualifying bean of type 'demo.Shard' available

同样代码注册 CloudStore。

同名冲突

实测 · 本机执行输出
  启动失败 BeanDefinitionOverrideException:Invalid bean definition with name 'dup' defined in null: Cannot register bean definition […class=demo.Shard…] for bean 'dup' since there is already […] bound.

BeanDefinitionOverrideException,信息里是 defined in null。

与 @Bean 同名

实测 · 本机执行输出
  启动失败 BeanDefinitionOverrideException:Invalid bean definition with name 'same' defined in null: Cannot register bean definition […class=demo.Shard…] for bean 'same' since there is already […] bound.

同样启动失败。

@ConditionalOnMissingBean

实测 · 本机执行输出
    构造 Shard(-1)
    构造 Shard(99)
  容器里 Shard 类型的 Bean 名:[fallbackShard, shardR]
    getBean("fallbackShard") 两次取到 Shard(-1),同一个对象
    getBean("shardR") 两次取到 Shard(99),同一个对象
    按类型取 getBean(Shard.class) 异常 NoUniqueBeanDefinitionException:No qualifying bean of type 'demo.Shard' available: expected single matching bean but found 2: fallbackShard,shardR

没有把 BeanRegistrar 注册的 Bean 当成已存在。

允许覆盖

实测 · 本机执行输出
    构造 Shard(1)
  容器里 Shard 类型的 Bean 名:[same]
    getBean("same") 两次取到 Shard(1),同一个对象
    按类型取 getBean(Shard.class) = Shard(1)

打开 allow-bean-definition-overriding 后不再报错,留下的是 BeanRegistrar 注册的那个。

分步演示

BeanRegistrar
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Boot 4.0.8 的 spring-boot 与 spring-boot-autoconfigure,不带 Web;BeanRegistrar 经 @Import 挂在配置类上,用 -Dmode=… 选择注册内容;Bean 数量、构造次数与类型均来自程序打印)
    • 没有验证:BeanRegistrar 在 GraalVM native / AOT 下的行为;order()、description()、backgroundInit()、infrastructure()、notAutowirable() 这些 Spec 选项;registerAlias;registrar 嵌套 register;@ConditionalOnMissingBean 看不到 registrar 注册的 Bean 的具体原因(没有查求值顺序),也没有测试反过来 @ConditionalOnBean 能不能看到;registrar 注册的 Bean 在 Actuator /beans 里的显示
    京ICP备2026067502号-1京公网安备11010502063487号