Programmatic Bean Registration with BeanRegistrar
Prior to Spring Framework 7, beans could be registered programmatically in several ways — for example, by implementing BeanDefinitionRegistryPostProcessor or ImportBeanDefinitionRegistrar, or by manipulating the ApplicationContext directly.
A custom BeanDefinitionRegistryPostProcessor might look like this:
public class MyBeanRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinitionBuilder postRepositoryBeanDef = BeanDefinitionBuilder.genericBeanDefinition(PostRepository.class);
registry.registerBeanDefinition("posts", postRepositoryBeanDef.getBeanDefinition());
}
}This approach is somewhat verbose and requires familiarity with Spring internals.
Another example is to register beans directly with an AnnotationConfigApplicationContext which is introduced in Spring 5:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
PostRepository posts = new PostRepository();
PostHandler postHandler = new PostHandler(posts);
Routes routesBean = new Routes(postHandler);
context.registerBean(PostRepository.class, () -> posts);
context.registerBean(PostHandler.class, () -> postHandler);
context.registerBean(Routes.class, () -> routesBean);
context.registerBean(WebHandler.class, () -> RouterFunctions.toWebHandler(routesBean.routes(), HandlerStrategies.builder().build()));
context.refresh();Here is the complete project of the above code snippet, see spring-reactive-sample/register-bean.
However, this technique also entails considerable boilerplate.
Spring Framework 7 introduces a more concise API for programmatic bean registration via a BeanRegistrar interface. For example, you might have two implementations of a PostRepository interface — one backed by R2DBC and another using an in-memory alternative.
Get Hantsy’s stories in your inbox
Join Medium for free to get updates from this writer.
The following example demonstrates conditional registration based on the active profile using BeanRegistrar:
class MyBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
if (env.matchesProfiles("h2")) {
// registry.registerBean(R2dbcConfig.class);
registry.registerBean(
PostRepository.class,
(BeanRegistry.Spec<PostRepository> spec) -> spec
.supplier(ctx -> new H2PostRepository(ctx.bean(DatabaseClient.class)))
);
} else {
registry.registerBean(
PostRepository.class,
(BeanRegistry.Spec<PostRepository> spec) -> spec
.supplier(ctx -> new InMemoryPostRepository())
);
}
registry.registerBean(PostHandler.class,
(BeanRegistry.Spec<PostHandler> spec) -> spec
.supplier(ctx -> new PostHandler(ctx.bean(PostRepository.class)))
);
registry.registerBean(Routes.class,
(BeanRegistry.Spec<Routes> spec) -> spec
.supplier(ctx -> new Routes(ctx.bean(PostHandler.class))));
registry.registerBean("webHandler", WebHandler.class,
(BeanRegistry.Spec<WebHandler> spec) -> spec
.prototype()
.supplier(ctx -> RouterFunctions.toWebHandler(ctx.bean(Routes.class).routes(), HandlerStrategies.builder().build()))
);
}
}Import this custom MyBeanRegistrar into a configuration class to activate it:
@Configuration
@Import(MyBeanRegistrar.class)
public class CustomConfig {
}BeanRegistrar provides fine-grained control over bean registration, including scope (singleton, prototype, etc.), lazy initialization, and custom suppliers for instantiation. It is also compatible with native-image compilation using GraalVM.
Annotating the test class with @ActiveProfiles("h2") activates the h2 profile, causing the H2PostRepository implementation to be registered and injected.
@SpringJUnitConfig(classes = {CustomConfig.class, R2dbcConfig.class})
@ActiveProfiles("h2")
class H2ApplicationTest {
@Autowired
PostRepository postRepository;
@Test
void testPostRepository() {
assertThat(this.postRepository).isInstanceOf(H2PostRepository.class);
}
@Test
void testCurdOperations() {
Post post = new Post(null, "Test Title", "Test Content");
Mono<UUID> savedPostId = this.postRepository.save(post);
savedPostId.flatMap(id -> this.postRepository.findById(id))
.as(StepVerifier::create)
.consumeNextWith(p -> {
assertThat(p.title()).isEqualTo("Test Title");
assertThat(p.content()).isEqualTo("Test Content");
})
.verifyComplete();
savedPostId.flatMap(id ->
this.postRepository.update(id, new Post(null, "Updated Title", "Updated Content"))
)
.subscribe(updatedCount -> System.out.println("Updated rows: " + updatedCount));
this.postRepository.findAll().subscribe(System.out::println);
savedPostId.flatMap(id -> this.postRepository.deleteById(id))
.subscribe(System.out::println);
this.postRepository.findAll().subscribe(System.out::println);
}
}Elsewhere, the InMemoryPostRepository will be used instead.
@SpringJUnitConfig(CustomConfig.class)
class ApplicationTest {
@Autowired
PostRepository postRepository;
@Test
void testPostRepository() {
assertThat(this.postRepository).isInstanceOf(InMemoryPostRepository.class);
}
}
Spring 7 additionally offers BeanRegistrarDsl, a Kotlin extension that enables a more fluent registration style with Kotlin DSL.
Here is an example written in Kotlin and utilizing BeanRegistrarDsl:
@Configuration
@Import(MyBeanRegistrar::class)
class CustomConfig
class MyBeanRegistrar : BeanRegistrarDsl({
registerBean { InMemoryPostRepository() }
registerBean { PostHandler(bean()) }
registerBean { Routes(bean()) }
registerBean(
name = "webHandler",
backgroundInit = true,
prototype = false,
supplier = {
RouterFunctions.toWebHandler(bean<Routes>().routes(), HandlerStrategies.builder().build())
}
)
register(FoobarRegistrar())
})
class FoobarRegistrar : BeanRegistrarDsl({
profile("foo") {
registerBean<Bar> { Bar(bean<Foo>()) }
registerBean { Foo() }
}
})
class Bar(foo: Foo)
class FooSee the repository examples for complete samples:











