selectAutoConfigurations function

List<BeanModule> selectAutoConfigurations(
  1. List<BeanModule> modules, {
  2. Map<BeanModule, AutoConfigurationModule> metadata = const {},
  3. Iterable<Type> excludeTypes = const [],
  4. Iterable<String> excludeNames = const [],
})

Takes the auto-configuration modules of the libraries an application uses, drops the excluded ones and returns the rest in the order they must be registered.

SpringApplication calls it on every start with the modules the generator found, the application's exclude / excludeName and the spring.autoconfigure.exclude property. Applications never call it; a test tool that builds a context from auto-configuration, such as ApplicationContextRunner, does.

Order matters because a library's @ConditionalOnMissingBean only sees the beans registered before it. The modules are sorted by AutoConfigurationModule.order (from metadata; 0 when absent), then by name, then by their position in modules. Then the before and after lists move modules around; a cycle among them throws a StateError (AutoConfigure cycle detected between ...).

excludeTypes and excludeNames name what to leave out. A name matches a module by its BeanModule.name, or an @AutoConfiguration class by its name (CacheAutoConfiguration) or full name (package:x/src/y.dart#CacheAutoConfiguration), found through the source of the bean definitions the class produced. Excluding a class removes its bean definitions, its @Imports (registrars and selectors) and its conditional @PropertySources; excluding a module removes all of it. A Type in excludeTypes that matches nothing throws a StateError (... could not be excluded because they are not auto-configuration classes); an unknown name is ignored, as Spring Boot ignores a class that is not on the classpath.

In before and after, a class is matched by its short name only.

Spring (Java): the exclusion of @SpringBootApplication(exclude = ...) and the order of AutoConfigurationSorter (by name, then @AutoConfigureOrder, then @AutoConfigureBefore / After).

Implementation

List<BeanModule> selectAutoConfigurations(
  List<BeanModule> modules, {
  Map<BeanModule, AutoConfigurationModule> metadata = const {},
  Iterable<Type> excludeTypes = const [],
  Iterable<String> excludeNames = const [],
}) {
  final typeNames = {for (final type in excludeTypes) '$type'};
  final names = {
    ...typeNames,
    for (final name in excludeNames)
      if (name.trim().isNotEmpty) name.trim(),
  };
  final matched = <String>{};
  final entries = <_Entry>[];
  for (final module in modules) {
    if (names.contains(module.name)) {
      matched.add(module.name);
      continue;
    }
    // Whether [source] belongs to an excluded class (recorded as matched).
    bool excluded(String? source) {
      final owner = _owner(source);
      final hit = owner == null
          ? const <String>{}
          : {owner.$1, owner.$2}.intersection(names);
      matched.addAll(hit);
      return hit.isNotEmpty;
    }

    final kept = [
      for (final definition in module.definitions)
        if (!excluded(definition.source)) definition,
    ];
    // The imports of the classes left (an import belongs to its importer,
    // else to the class of its condition group).
    final keptImports = [
      for (final declaration in module.registrars)
        if (!excluded(
          declaration.importer ??
              declaration.conditions
                  .whereType<ConditionGroup>()
                  .firstOrNull
                  ?.key,
        ))
          declaration,
    ];
    final keptSources = [
      for (final declaration in module.propertySources)
        if (!excluded(
          declaration.conditions.whereType<ConditionGroup>().firstOrNull?.key,
        ))
          declaration,
    ];
    final info = metadata[module];
    entries.add(
      _Entry(
        kept.length == module.definitions.length &&
                keptImports.length == module.registrars.length &&
                keptSources.length == module.propertySources.length
            ? module
            : _withDefinitions(module, kept, keptImports, keptSources),
        info?.order ?? 0,
        info?.before ?? const [],
        info?.after ?? const [],
        {
          for (final definition in module.definitions)
            ?_owner(definition.source)?.$1,
        },
      ),
    );
  }
  final missing = typeNames.difference(matched);
  if (missing.isNotEmpty) {
    throw StateError(
      'The following classes could not be excluded because they are not '
      'auto-configuration classes:\n'
      '${[for (final name in missing) '\t- $name'].join('\n')}',
    );
  }
  return _sort(entries);
}