
# 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](conditional.md) cover the usual cases.

Use a registrar in one of two ways:

- put the class in [@Import](import.md) on a configuration class. The generator creates it with its no-argument constructor;
- pass an instance to `context.register(...)` before `refresh()`.

`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:

- `ImportSelector` returns the [BeanModule](bean-module.md)s to load, chosen at startup. `DeferredImportSelector` runs after every other module, so its beans can back off.
- `ImportBeanDefinitionRegistrar` registers full `BeanDefinition`s and can see, replace or remove the ones already registered.

## Example

```dart
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:

```dart
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`:

```java
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 `Spec` builder.
- An `ImportSelector` returns `BeanModule`s, not class names, and receives no annotation metadata of the importing class.

## See also

- [@Import](import.md)
- [BeanModule](bean-module.md)
- [BeanFactoryPostProcessor](bean-factory-post-processor.md) (`BeanDefinitionRegistryPostProcessor`)
- [@Conditional](conditional.md)
- [@Profile](profile.md)
- [ApplicationContext](application-context.md) (`register`, `registerBean`)
- [Environment](../config/environment.md)
