@Scope
@Scope(...) sets how many instances of a bean exist and how long each one lives.
Why
Without @Scope a bean is a singleton: one instance for the whole context, created at startup and shared by everyone who injects it. That is right for most services.
Choose another scope when a bean holds state that must not be shared:
ScopeType.prototype: a new instance for every injection and every lookup. For builders, commands, objects with per-use state. The container does not track prototypes, so their@PreDestroymethods never run.- a custom scope name: one instance per span that you define, such as a screen, a session or a job. Register the scope on the bean factory with
registerScopebefore the context starts.
Shorter scopes inside singletons
A singleton keeps what it was injected with. A screen-scoped bean injected directly into a singleton would stay the first screen's instance forever (the container logs a warning). With proxyMode: ScopedProxyMode.targetClass (or interfaces) the singleton gets a generated proxy instead. Every call on the proxy asks the scope for the current instance. An ObjectProvider is the other way to reach the current instance.
The proxied type must be implementable from generated code: an interface, or a class without private members, not generic.
Example
import 'package:spring_core_dart/spring_core_dart.dart';
// Each user of a cart gets their own instance.
@Component()
@Scope(ScopeType.prototype)
class ShoppingCart {
final items = <String>[];
}
// One instance per open screen. Services see the current screen's
// state through a generated proxy.
@Component()
@Scope('screen', proxyMode: ScopedProxyMode.targetClass)
class ScreenState {
String title = '';
@PreDestroy()
void clear() => title = '';
}
@Service()
class Analytics {
Analytics(this.state); // the proxy, not a fixed instance
final ScreenState state;
String currentScreen() => state.title;
}
Registering the custom scope and ending it:
// SimpleMapScope keeps instances in a map until destroy() is called.
final screen = SimpleMapScope(conversationId: 'checkout');
final context = AnnotationConfigApplicationContext([appBeans]);
context.beanFactory.registerScope('screen', screen); // before refresh
context.refresh();
context.getBean<ScreenState>().title = 'Checkout';
// ... when the screen closes:
screen.destroy(); // runs @PreDestroy of ScreenState; next lookup is fresh
A scope of your own implements CustomScope (get, remove, registerDestructionCallback, conversationId).
Parameters
| Name | Type | Default | Meaning |
|---|---|---|---|
value (positional) |
String |
required | The scope name: ScopeType.singleton, ScopeType.prototype or the name of a registered custom scope. |
proxyMode |
ScopedProxyMode |
ScopedProxyMode.no |
no injects the instance directly. interfaces and targetClass inject a generated proxy that asks the scope on every call. |
Related types:
| Type | Meaning |
|---|---|
ScopeType.singleton / ScopeType.prototype |
The names of the two built-in scopes. |
ScopedProxyMode |
no, interfaces, targetClass. |
CustomScope |
The interface of a custom scope (Spring's Scope). |
SimpleMapScope |
A ready-made custom scope backed by a map, emptied by destroy(). |
In Spring (Java)
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class ShoppingCart { ... }
@Component
@Scope(value = "screen", proxyMode = ScopedProxyMode.TARGET_CLASS)
class ScreenState { ... }
beanFactory.registerScope("screen", new MyScreenScope());
Differences that matter:
- Spring's scope interface is called
Scope. Here that name is the annotation, and the interface isCustomScope. - There is no
ScopedProxyMode.DEFAULT(defaultis a Dart keyword);nois the default. interfacesandtargetClassboth generate a class that implements the bean's type, because every Dart class is also an interface.- The target is not registered as a separate
scopedTarget.bean.