BeanModule
A BeanModule holds the bean definitions the generator found in one scanned directory, with the property files, imports and embedded resources that come with them.
Why
Dart in Flutter and AOT executables cannot search a program for annotated classes at run time. So @ComponentScan makes the generator write a module at build time, and the application passes its modules to the context:
final context = ApplicationContext.run([appBeans]);
You normally never construct a module. Annotate a top-level variable with @ComponentScan() and initialize it with the generated module, which has the same name prefixed with $:
@ComponentScan()
final BeanModule appBeans = $appBeans;
A library ships its beans as a module of its own. The application lists it next to its own module, or imports it.
Write a module by hand for a few beans in a test or a plug-in.
What a module contains
definitions: the bean definitions. Each is registered only if its conditions match.propertySources: the@PropertySourcedeclarations of its classes. They are loaded before the definitions are decided, so conditions see the properties.imports: modules loaded before this one: those of nested@ComponentScandirectories and of@Imported configuration classes.registrars: the@Imported BeanRegistrars,ImportSelectors andImportBeanDefinitionRegistrars.resources: the files of the package'slib/resources/, embedded by the generator. They are served asresources:locations, also in AOT, Flutter and the web. They are registered as soon as the module is created, so configuration files read before the context starts can find them.- generated helpers: the injectors of
@Configurableclasses, the descriptions of@Introspectedclasses, and type tests used by annotations and AOP.
When the context loads a module, it first loads its imports, then adds its propertySources to the environment, then registers each definition whose conditions match and runs its registrars. A module imported twice registers its beans once.
Adding modules
- Pass several modules to
ApplicationContext.run([...])or to theAnnotationConfigApplicationContextconstructor. - Call
context.register(module)beforerefresh(), for modules chosen at run time. - Return modules from an
ImportSelector, to choose a group of beans from the configuration.
Example
// lib/src/beans.dart
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/beans.spring.dart';
@ComponentScan()
final BeanModule appBeans = $appBeans;
// test/orders_test.dart
import 'package:my_app/src/beans.dart';
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:test/test.dart';
abstract interface class Clock {
DateTime now();
}
class FixedClock implements Clock {
@override
DateTime now() => DateTime.utc(2026, 1, 1);
}
// A hand-written module with a fixed clock and a test property file.
final testBeans = BeanModule(
'testBeans',
[
BeanDefinition<FixedClock>(
name: 'clock',
types: const [FixedClock, Clock],
primary: true,
create: (_) => FixedClock(),
),
],
propertySources: const [
PropertySourceDeclaration(
['file:test/test.properties'],
ignoreResourceNotFound: true,
),
],
);
void main() {
test('uses the fixed clock', () {
final context = ApplicationContext.run([appBeans, testBeans]);
expect(context.getBean<Clock>().now().year, 2026);
context.close();
});
}
Parameters
BeanModule(name, definitions, {...}):
| Name | Type | Default | Meaning |
|---|---|---|---|
name (positional) |
String |
required | The module name, shown in logs and errors. By convention the name of the annotated variable. |
definitions (positional) |
Iterable<BeanDefinition<Object>> |
required | The bean definitions. |
propertySources |
List<PropertySourceDeclaration> |
[] |
Property files to add to the environment. |
imports |
List<BeanModule> |
[] |
Modules loaded before this one. |
registrars |
List<ImportDeclaration> |
[] |
Registrars and selectors to run, each wrapped in an ImportDeclaration. |
resources |
Map<String, EmbeddedResource> |
{} |
Embedded files served as resources:. |
The other parameters (configurers, configurablePostConstruct, configurablePreDestroy, introspections, typeTests, typeDepths, aopProxies) are written by the generator.
PropertySourceDeclaration(locations, {...}):
| Name | Type | Default | Meaning |
|---|---|---|---|
locations (positional) |
List<String> |
required | Resource locations. May contain ${...} placeholders. |
name |
String? |
null |
The property source name. Derived from the location when null. |
ignoreResourceNotFound |
bool |
false |
Whether a missing file is skipped instead of failing the refresh. |
factory |
PropertySourceFactory |
DefaultPropertySourceFactory() |
Turns each file into a property source. |
encoding |
String? |
null |
The charset of the files. UTF-8 when null. |
conditions |
List<Condition> |
[] |
The files are loaded only when all match. |
ImportDeclaration(create, {conditions, importer}): create is a function that returns the registrar or selector, such as MessagingRegistrar.new.
In Spring (Java)
Spring has no direct counterpart. A module is what classpath scanning plus @Configuration parsing would register:
var context = new AnnotationConfigApplicationContext();
context.scan("com.example.app"); // like appBeans
context.registerBean("clock", Clock.class, FixedClock::new);
context.refresh();
Differences that matter:
- Spring scans packages at startup. Here the scan happens at build time and the result is a value you pass to the context.
- Files under
lib/resources/are embedded in the module, where Spring readsclasspath:resources from the class path.