BeanRegistrar
A BeanRegistrar registers beans in code, deciding at startup how many beans, which ones and under which names.
Why
Annotations declare a fixed set of beans. A registrar decides in code, based on the configuration:
- a library that registers one bean per configured entry;
- a bean that exists only when a property is set;
- an implementation picked from a property.
Most applications do not need one. @Profile and @Conditional cover the usual cases.
Use a registrar in one of two ways:
- put the class in @Import on a configuration class. The generator creates it with its no-argument constructor;
- pass an instance to
context.register(...)beforerefresh().
register runs once, while the modules load. No bean exists yet. It runs right after the importing class's @Bean methods are registered, and only when that class is registered: the class's conditions decide for the class and its imports together. Every later class and auto-configuration sees the beans, so their @ConditionalOnMissingBean backs off. The registrar can read the environment, including the @PropertySource files loaded so far.
The supplier creates the bean when it is needed. It receives a DependencyResolver to get the bean's own dependencies: context.dependency<Clock>(), context.optionalDependency<Clock>(), context.dependencyList<Handler>(), context.value<int>(r'${port:8080}').
Two lower-level forms exist:
ImportSelectorreturns the BeanModules to load, chosen at startup.DeferredImportSelectorruns after every other module, so its beans can back off.ImportBeanDefinitionRegistrarregisters fullBeanDefinitions and can see, replace or remove the ones already registered.
Example
import 'package:spring_core_dart/spring_core_dart.dart';
abstract interface class Clock {
DateTime now();
}
class SystemClock implements Clock {
@override
DateTime now() => DateTime.now();
}
abstract interface class Mailer {
void send(String to, String text);
}
class SmtpMailer implements Mailer {
SmtpMailer(this.host, this.clock);
final String host;
final Clock clock;
@override
void send(String to, String text) {}
}
class Tenant {
Tenant(this.id);
final String id;
}
// A clock always; an SMTP mailer only when a mail host is configured;
// one Tenant bean per entry of `tenants`.
class MessagingRegistrar implements BeanRegistrar {
const MessagingRegistrar();
@override
void register(BeanRegistry registry, Environment environment) {
registry.registerBean<Clock>((_) => SystemClock(), name: 'clock');
final host = environment.getProperty('mail.host');
if (host != null) {
registry.registerBean<Mailer>(
(context) => SmtpMailer(host, context.dependency<Clock>()),
name: 'mailer',
primary: true,
lazy: true,
description: 'SMTP mailer',
);
}
final tenants = environment.getProperty('tenants') ?? '';
for (final id in tenants.split(',').where((id) => id.isNotEmpty)) {
registry.registerBean<Tenant>((_) => Tenant(id), name: 'tenant-$id');
}
}
}
@Configuration()
@Import([MessagingRegistrar])
class AppConfig {}
Outside a registrar, AnnotationConfigApplicationContext.registerBean takes the same options:
final context = AnnotationConfigApplicationContext([appBeans])
..registerBean<Clock>((_) => SystemClock(), name: 'clock')
..refresh();
Members
BeanRegistrar:
| Method | Meaning |
|---|---|
void register(BeanRegistry registry, Environment environment) |
Registers beans in registry, deciding with the help of environment. |
BeanRegistry:
| Method | Meaning |
|---|---|
String registerBean<T>(supplier, {...}) |
Registers a bean of T created by supplier. Returns its name. |
void registerAlias(String name, String alias) |
Makes the bean name also available as alias. |
void register(BeanRegistrar registrar) |
Runs another registrar on this registry, to compose registrars. |
Options of registerBean:
| Name | Type | Default | Meaning |
|---|---|---|---|
supplier (positional) |
T Function(DependencyResolver context) |
required | Creates the instance. |
name |
String? |
T#0 (T#1, ... if taken) |
The bean name. |
types |
List<Type>? |
[T] |
The types the bean is found by. |
scope |
String |
ScopeType.singleton |
The scope, such as ScopeType.prototype. |
lazy |
bool |
false |
Create on first use. |
primary |
bool |
false |
Like @Primary. |
fallback |
bool |
false |
Like @Fallback. |
autowireCandidate |
bool |
true |
false keeps the bean out of injection by type. |
defaultCandidate |
bool |
true |
false injects it only where a qualifier selects it. |
order |
int? |
null |
Position in ordered collections. |
description |
String? |
null |
A text for tools. |
aliases |
List<String> |
[] |
More names. |
dependsOn |
List<String> |
[] |
Beans created before this one. |
In Spring (Java)
Spring 7's BeanRegistrar:
class MessagingRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean("clock", SystemClock.class);
if (env.containsProperty("mail.host")) {
registry.registerBean("mailer", Mailer.class, spec -> spec
.supplier(context -> new SmtpMailer(context.bean(Clock.class)))
.primary().lazyInit().description("SMTP mailer"));
}
}
}
@Configuration
@Import(MessagingRegistrar.class)
class AppConfig {}
Differences that matter:
- The supplier is required. Dart has no reflection to call a constructor.
- The options are named parameters instead of a
Specbuilder. - An
ImportSelectorreturnsBeanModules, not class names, and receives no annotation metadata of the importing class.
See also
- @Import
- BeanModule
- BeanFactoryPostProcessor (
BeanDefinitionRegistryPostProcessor) - @Conditional
- @Profile
- ApplicationContext (
register,registerBean) - Environment