Skip to content

编程式 Bean 注册 (Programmatic Bean Registration)

从 Spring Framework 7 开始,通过 BeanRegistrar 接口提供了对编程式 Bean 注册的一流支持。你可以实现该接口,以一种灵活且高效的方式编程式地注册 Bean。

这些 Bean 注册器(Bean Registrar)实现通常通过在 @Configuration 类上使用 @Import 注解来导入。

java
@Configuration
@Import(MyBeanRegistrar.class)
class MyConfiguration {
}
kotlin
@Configuration
@Import(MyBeanRegistrar::class)
class MyConfiguration {
}

提示

你可以利用类型级的条件注解(@Conditional 以及其它变体),有条件地导入相关的 Bean 注册器。

Bean 注册器实现使用 BeanRegistryEnvironment API,以简洁灵活的方式编程式地注册 Bean。例如,它允许通过 if 表达式、for 循环等方式进行自定义注册。

java
class MyBeanRegistrar implements BeanRegistrar {

	@Override
	public void register(BeanRegistry registry, Environment env) {
		registry.registerBean("foo", Foo.class);
		registry.registerBean("bar", Bar.class, spec -> spec
				.prototype()
				.lazyInit()
				.description("Custom description")
				.supplier(context -> new Bar(context.bean(Foo.class))));
		if (env.matchesProfiles("baz")) {
			registry.registerBean(Baz.class, spec -> spec
					.supplier(context -> new Baz("Hello World!")));
		}
		registry.registerBean(MyRepository.class);
		registry.registerBean(RouterFunction.class, spec ->
				spec.supplier(context -> router(context.bean(MyRepository.class))));
	}

	RouterFunction<ServerResponse> router(MyRepository myRepository) {
		return RouterFunctions.route()
				// ...
				.build();
	}

}
kotlin
class MyBeanRegistrar : BeanRegistrarDsl({
	registerBean<Foo>()
	registerBean(
		name = "bar",
		prototype = true,
		lazyInit = true,
		description = "Custom description") {
		Bar(bean<Foo>()) // 也可以写作 Bar(bean())
	}
	profile("baz") {
		registerBean { Baz("Hello World!") }
	}
	registerBean<MyRepository>()
	registerBean {
		myRouter(bean<MyRepository>()) // 也可以写作 myRouter(bean())
	}
})

fun myRouter(myRepository: MyRepository) = router {
	// ...
}

注意

Bean 注册器支持运行前优化 (AOT),无论是在 JVM 上还是在使用 GraalVM 原生镜像时,即使使用了实例供应器(instance suppliers)也是如此。


补充教学 —— BeanRegistrar:Spring 7 开启的配置新篇章

在 Spring 7 之前,如果我们想动态注册 Bean,通常需要触及底层的 ImportBeanDefinitionRegistrar。虽然功能强大,但 API 相对原始且繁琐。BeanRegistrar 的出现标志着动态配置进入了“现代化”阶段。

1. 为什么我们需要 BeanRegistrar

  • 不仅仅是静态定义@Bean 注解适合静态的、明确的 Bean 定义。但如果你需要根据数据库里的某张配置表、或者某个文件夹下的配置文件来动态生成一堆 Bean,BeanRegistrarfor 循环注册就是天生利器。
  • 更强的函数式支持:通过 spec.supplier(...),你可以完全控制 Bean 的实例化逻辑,这比传统的反射实例化更高效且类型安全。
  • 精简的 DSL:特别是对于 Kotlin 开发者,BeanRegistrarDsl 让配置代码看起来就像官方原生语言的一部分。

2. BeanRegistrar 与传统方式的对比

  • VS @Bean@Bean 简单直观,但由于依赖代理(Full 模式),在大量 Bean 注册时有微小的性能开销。BeanRegistrar 直接与 BeanRegistry 交互,更加底层且高效。
  • VS ImportBeanDefinitionRegistrarBeanRegistrar 是前者的后继者和简化版。它不再强制你处理深奥的 BeanDefinition 属性,而是提供了一个流式(Fluent)API 来设置作用域、延迟初始化等。

3. 它是如何支持 AOT 的? 这是 Spring 7 的一大卖点。以前的动态注册往往依赖大量反射和动态字节码生成,这使得它们很难被 GraalVM 静态分析。BeanRegistrar 的设计从底层就考虑了 AOT:

  • 可预测性:它提供的 API 结构化程度高,Spring 的 AOT 引擎可以轻松转化这些编程式逻辑。
  • Supplier 的优势:Lambda 形式的 supplier 在编译期就能被转化为确定性的实例化代码,极大地提升了原生镜像的启动速度和兼容性。

4. 最佳实践

  • 配合 @Conditional:不要在 register 方法里写太复杂的逻辑。利用 @Import 类上的条件注解来控制整个注册器的开关,保持职责单一。
  • 优先使用参数注入获取其它 Bean:在 supplier 里,推荐使用 context.bean(Type.class) 来按类型获取依赖,这能保证在复杂的依赖图中正确排序。

总结:如果你正在编写一个 Spring 插件、或者需要处理复杂的动态业务 Bean 场景,请毫不犹豫地拥抱 BeanRegistrar。它是 Spring 迈向“云原生”和“函数式配置”的关键一步。

Based on Spring Framework.