ObjectProvider

ObjectProvider<T> is a handle on the beans of T that looks them up only when asked: one bean, the bean if there is one, or all of them.

Why

Inject ObjectProvider<T> instead of T when:

An injected provider resolves like the bean's other injection points. The bean itself, and the beans of its own @Bean methods, are left out. The parameter or field name selects among several candidates. Only autowire candidates take part. The beans it returns are recorded as dependencies, so they are destroyed after the bean that holds the provider. A @Primary decorator can therefore reach the other beans of its own type through a provider.

The provider of getBeanProvider<T>() is a plain lookup: getObject() works like getBean<T>(), and iteration includes every bean of T, also from parent factories.

An ObjectProvider is an Iterable<T>. Iterating creates the matching beans one by one, in registration order. orderedList() sorts them. isEmpty, length and toString create no beans.

For a single bean that is always there but should be resolved late, @Lazy on the injection point is an alternative: it injects a proxy of the type itself.

Example

import 'package:spring_core_dart/spring_core_dart.dart';

abstract interface class Exporter {
  void run();
}

abstract interface class Clock {
  DateTime now();
}

class SystemClock implements Clock {
  @override
  DateTime now() => DateTime.now();
}

class Session {
  void print() {}
}

@Service()
class Reports {
  Reports(this.exporters, this.clocks, this.sessions);

  // Exporters are optional plug-ins: there may be none, one or several.
  final ObjectProvider<Exporter> exporters;
  // A configured clock, or the system clock.
  final ObjectProvider<Clock> clocks;
  // A prototype or scoped bean: the current instance on every call.
  final ObjectProvider<Session> sessions;

  void exportFirst() => exporters.ifAvailable((e) => e.run());

  void exportAll() {
    for (final exporter in exporters.orderedList()) {
      exporter.run();
    }
  }

  DateTime now() => clocks.getIfAvailable(SystemClock.new)!.now();

  void printSession() => sessions.getObject().print();
}

Outside a bean:

final greeter = context.getBeanProvider<Greeter>().getIfUnique();
final all = context.getBeanProvider<Greeter>().orderedList();

Members

Method Meaning
T getObject() The matching bean, created if needed. Throws NoSuchBeanDefinitionException when none matches and NoUniqueBeanDefinitionException when several match and none wins.
T get() The same as getObject(), under the name of JSR-330's Provider.get.
T? getIfAvailable([T Function()? defaultSupplier]) The matching bean; when none matches, the supplier's result, or null. A bean whose scope is not active counts as missing. Still throws when several match and none wins.
T? getIfUnique([T Function()? defaultSupplier]) The matching bean; the supplier's result, or null, when none or several match. Never throws for those.
void ifAvailable(void Function(T bean) consumer) Calls consumer when getIfAvailable finds a bean.
void ifUnique(void Function(T bean) consumer) Calls consumer when getIfUnique finds a bean.
List<T> orderedList() Every matching bean, sorted: PriorityOrdered first, then by Ordered.order, @Order or @Priority, then by registration. Empty when none match.
Iterable<T> members forEach, map, where, for (... in provider): every matching bean, in registration order.

When several beans match, getObject() and getIfAvailable() choose like injection does: the single @Primary bean, else the only one not marked @Fallback, else the one whose name matches.

ObjectFactory<T> is the smallest form, with only getObject(). An ObjectFactory<T> injection point receives an ObjectProvider<T>. Prefer ObjectProvider in new code. A jakarta Provider<T> injection point receives one too.

In Spring (Java)

@Service
class Reports {
  private final ObjectProvider<Exporter> exporters;

  Reports(ObjectProvider<Exporter> exporters) {
    this.exporters = exporters;
  }

  void exportFirst() { exporters.ifAvailable(Exporter::run); }
  void exportAll() { exporters.orderedStream().forEach(Exporter::run); }
}

Differences that matter:

See also