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:

Most applications do not need one. @Profile and @Conditional cover the usual cases.

Use a registrar in one of two ways:

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:

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:

See also