How it works
The explanation, with examples you can run.
The scopes you will meet
| Scope | Instances | Typical use |
|---|---|---|
singleton (default) | One per container | Services, repositories, clients |
prototype | A new one each time it is requested from the container | Stateful helpers, builders |
request | One per HTTP request | Per-request context |
session | One per HTTP session | Shopping basket in a server-rendered app |
application | One per ServletContext | Rarely needed |
@Component@Scope("prototype")class ReportBuilder { private final List<String> lines = new ArrayList<>(); } @Serviceclass ReportService { private final ObjectProvider<ReportBuilder> builders; ReportService(ObjectProvider<ReportBuilder> builders) { this.builders = builders; } String build() { ReportBuilder b = builders.getObject(); // fresh instance each call ... }}Injecting the prototype directly into the constructor would give the singleton one builder for life, which is a classic surprise. ObjectProvider (or a scoped proxy, which is how request and session beans can be injected into singletons) defers the lookup to the moment you need it.
From constructor to destruction
For each singleton, the container goes through the same steps:
- Call the constructor (with constructor-injected dependencies).
- Inject fields and setters, if any.
- Run
BeanPostProcessors before initialisation, then the init callbacks:@PostConstructfirst, thenInitializingBean.afterPropertiesSet(), then a custom init method. - Run
BeanPostProcessors after initialisation. This is where Spring may return a proxy instead of your object, for@Transactional,@Async,@Cacheableand similar. - The bean is ready. On a clean shutdown,
@PreDestroy(and other destroy callbacks) run in reverse order.
@Componentclass PriceCache { @PostConstruct void warm() { /* every dependency is set by now */ } @PreDestroy void flush() { /* runs on graceful shutdown */ }}In Boot 3 these annotations come from jakarta.annotation, not javax.annotation. One detail: Spring does not track prototype beans after handing them out, so their destroy callbacks are not called; cleaning them up is your job.
Why step 4 matters
The object other beans receive may not be the class you wrote, but a generated subclass that wraps it. Most of the surprises in the transactions topic, such as an annotation that "does nothing", come from forgetting that the proxy exists and when it is bypassed.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.