@ComponentScan
@ComponentScan() makes the generator write a module with every bean declared in the directory of the annotated file and its subdirectories.
Why
Dart in Flutter and AOT executables cannot search the program for annotated classes at run time. So the search happens at build time: build_runner runs spring_core_dart_generator, which reads the files under the directory and writes a BeanModule. You pass that module to the context.
Put @ComponentScan() on a top-level variable. Initialize it with the generated module, which has the same name prefixed with $. The generated file sits under lib/spring_gen/, mirroring the path of your file.
A Spring Boot application uses @SpringBootApplication() instead, which scans too.
Nested scans: a subdirectory with its own @ComponentScan belongs to that scan, and its filters apply there. The enclosing scan imports its module instead of scanning its files again, so every bean is registered once. One directory has one scan root.
Filters
Filters change what counts as a bean. They are evaluated at build time.
includeFilters: classes matching one of them are beans even without@Component. Abstract classes never are.excludeFilters: classes matching one of them are never beans, whatever else says. Their@Beanmethods are skipped with them.useDefaultFilters: false:@Component(and its subclasses) no longer marks a bean; only the include filters do.lazyInit: true: every scanned bean is created on first use, unless annotated@Lazy(false).
A Filter matches in one of three ways (FilterType):
type |
Uses | Matches a class that |
|---|---|---|
FilterType.annotation (default) |
classes |
carries one of the annotations (or a subclass of one). |
FilterType.assignableType |
classes |
is, extends, implements or mixes in one of the types. |
FilterType.regex |
pattern |
has a name matching one of the regular expressions. |
Example
// lib/src/beans.dart: everything under lib/src/ is scanned.
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/beans.spring.dart';
@ComponentScan()
final BeanModule appBeans = $appBeans;
// lib/src/plugins/beans.dart: a scan with filters.
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/plugins/beans.spring.dart';
// Every Plugin class is a bean without @Component, classes whose name
// ends in Stub never are, and every bean is created on first use.
@ComponentScan(
includeFilters: [
Filter(type: FilterType.assignableType, classes: [Plugin]),
],
excludeFilters: [
Filter(type: FilterType.regex, pattern: [r'.*Stub$']),
],
lazyInit: true,
)
final BeanModule pluginBeans = $pluginBeans;
abstract interface class Plugin {
String get id;
}
// bin/main.dart
void main() {
// appBeans already imports pluginBeans (nested scan); passing both
// is harmless.
final context = ApplicationContext.run([appBeans]);
context.close();
}
Run the generator after changing annotations:
dart run build_runner build
Parameters
ComponentScan:
| Name | Type | Default | Meaning |
|---|---|---|---|
includeFilters |
List<Filter> |
[] |
Classes matching one of these are beans even without @Component. |
excludeFilters |
List<Filter> |
[] |
Classes matching one of these are never beans. |
useDefaultFilters |
bool |
true |
Whether @Component and its subclasses mark a bean. |
lazyInit |
bool |
false |
Whether the scanned beans are created on first use. |
Filter:
| Name | Type | Default | Meaning |
|---|---|---|---|
type |
FilterType |
FilterType.annotation |
How the filter matches. |
classes |
List<Type> |
[] |
Annotation classes, or types the class must be assignable to. |
pattern |
List<String> |
[] |
Regular expressions matched against the class name. |
In Spring (Java)
@Configuration
@ComponentScan(
basePackages = "com.example.plugins",
includeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE, classes = Plugin.class),
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX, pattern = ".*Stub"),
lazyInit = true)
class PluginConfig {}
var context = new AnnotationConfigApplicationContext(PluginConfig.class);
Differences that matter:
- Spring scans the classpath at startup, by package. Here the generator scans a directory at build time, and the result is a variable you pass to the context.
- There are no
basePackages: the directory of the annotated file is the base. FilterType.ASPECTJandFilterType.CUSTOMdo not exist.