πΏ Spring Context Lifecycle β From Startup to Shutdown¶
Mental model¶
Spring is like a living organism: the ApplicationContext is its brain, and your beans are the organs. At startup, Spring builds, injects, activates, and eventually disposes every bean in a predictable lifecycle β all orchestrated with reflection.
π§ High-level flow¶
Spring Boot starts β
creates ApplicationContext β
scans classpath β
registers beans β
instantiates beans β
injects dependencies β
calls lifecycle callbacks β
ready to serve
At shutdown, Spring reverses the flow:
βCall destroy methods β close context β free resources.β
π§© Key actors in the lifecycle¶
| Role | Description |
|---|---|
| ApplicationContext | The central Spring container (brain). Manages bean creation, wiring, events, and lifecycle. |
| BeanDefinition | Metadata about each bean (class, scope, dependencies, lifecycle methods). |
| BeanFactory | Core interface that actually creates and manages beans (ApplicationContext extends it). |
| BeanPostProcessor | Hooks to modify beans after instantiation but before use. |
| Environment | Provides configuration properties. |
| EventPublisher | Broadcasts application events (ContextRefreshedEvent, etc.). |
βοΈ Step-by-step startup sequence¶
1οΈβ£ Bootstrapping¶
Spring Bootβs SpringApplication.run():
- Creates an ApplicationContext (usually
AnnotationConfigApplicationContext). - Prepares the Environment (properties, profiles).
- Starts component scanning.
2οΈβ£ Scanning & registration¶
- Spring scans the classpath for
@Component,@Service,@Repository,@Controller, and@Configuration. -
For each, it builds a
BeanDefinition: -
Bean class (
Class<?>) - Scope (
singleton,prototype, etc.) - Dependencies (
@Autowired,@Value) - Lifecycle callbacks (
@PostConstruct,@PreDestroy)
No beans are created yet β only registered in the context.
3οΈβ£ Instantiation & dependency injection¶
- Spring goes through all registered beans.
- It creates instances reflectively using
getDeclaredConstructor().newInstance(). -
Then performs dependency injection:
-
Constructor injection: calls constructor with dependencies.
- Field injection: sets fields via reflection (
Field.setAccessible(true)). - Setter injection: invokes setter methods reflectively.
After wiring, the bean is now constructed but not yet active.
4οΈβ£ Post-processing¶
Now, BeanPostProcessors kick in β these are internal hooks that let Spring modify beans before and after initialization.
For example:
AutowiredAnnotationBeanPostProcessorβ performs@Autowiredinjection.CommonAnnotationBeanPostProcessorβ handles@PostConstructand@PreDestroy.AopProxyCreatorβ wraps beans in proxies for@Transactional,@Async, etc.
Custom post-processors can also modify beans dynamically.
5οΈβ£ Initialization phase¶
If a bean implements certain interfaces or annotations, Spring calls them in order:
| Mechanism | Example | Purpose |
|---|---|---|
InitializingBean.afterPropertiesSet() |
custom init logic | Runs after dependency injection |
@PostConstruct |
annotated method | Same purpose, modern style |
XML attribute init-method |
(legacy) | Run after properties set |
At this point, the bean is fully initialized and enters the live context.
6οΈβ£ Application ready phase¶
When all beans are initialized:
ApplicationContextpublishesContextRefreshedEvent.@EventListenerandApplicationListenerbeans react.- Spring Boot runs
CommandLineRunnerandApplicationRunnerbeans. - The app begins serving requests (for web apps, the embedded server starts).
π§© Visual map¶
1. Scan classes (@Component)
2. Register BeanDefinitions
3. Instantiate via reflection
4. Inject @Autowired dependencies
5. Call @PostConstruct / init-method
6. ContextRefreshedEvent fired
7. Beans fully active
Shutdown:
1. Publish ContextClosedEvent
2. Call @PreDestroy / destroy-method
3. Clear caches & ClassLoaders
4. Exit JVM
βοΈ Shutdown & cleanup¶
When the JVM shuts down or you call context.close():
- Spring publishes
ContextClosedEvent. - Calls
@PreDestroymethods andDisposableBean.destroy(). - Destroys singletons and releases resources.
- Unloads classes if ClassLoaders are modularized (in app servers).
π§© Key lifecycle annotations and interfaces¶
| Type | Example | When called |
|---|---|---|
@PostConstruct |
public void init() |
After dependencies injected |
@PreDestroy |
public void cleanup() |
Before context shutdown |
InitializingBean |
afterPropertiesSet() |
After injection, before use |
DisposableBean |
destroy() |
On shutdown |
ApplicationContextAware |
setApplicationContext() |
Inject context reference |
@EventListener |
onApplicationEvent() |
On published events |
π§ Reflection behind the scenes¶
Reflection drives every lifecycle stage:
- Classpath scanning (
Class.forName) - Bean creation (
Class<?>.newInstance()) - Dependency wiring (
Field.setAccessible(true)) - Lifecycle methods (
Method.invoke) - Event publishing (
find annotated listener β invoke)
But once beans are ready, normal Java calls take over β reflection happens mostly during startup.
β‘ Lifecycle timings (approximate)¶
| Phase | Frequency | Reflection heavy? |
|---|---|---|
| Scanning | Once | β Yes |
| Instantiation | Once per bean | β Yes |
| Injection | Once per bean | β Yes |
| Post-processing | Once per bean | β Moderate |
| Ready state | Continuous | β No |
| Shutdown | Once | β Yes (for destroy methods) |
π§© Simplified pseudo flow¶
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
for (String name : ctx.getBeanDefinitionNames()) {
BeanDefinition def = ctx.getBeanFactory().getBeanDefinition(name);
Object bean = ctx.getBean(name); // triggers reflection creation/injection
}
ctx.close(); // triggers destroy phase
π§© Common pitfalls¶
| Symptom | Likely cause | Fix |
|---|---|---|
BeanCurrentlyInCreationException |
Circular dependencies | Refactor to setter or constructor injection |
NullPointerException on injected field |
Bean not in component scan path | Check @ComponentScan |
IllegalStateException: Context closed |
Accessing context after shutdown | Manage lifecycle correctly |
@PostConstruct not called |
Method not public or wrong signature | Must be public void with no args |
π§© Useful debugging hooks¶
@SpringBootApplication
public class DebugApp implements CommandLineRunner {
@Autowired ApplicationContext ctx;
public void run(String... args) {
Arrays.stream(ctx.getBeanDefinitionNames())
.forEach(System.out::println);
}
}
To trace lifecycle events:
@Component
public class EventsTracer implements ApplicationListener<ApplicationEvent> {
public void onApplicationEvent(ApplicationEvent e) {
System.out.println("Event β " + e.getClass().getSimpleName());
}
}
π§© TL;DR Summary¶
| Stage | Description | Reflection role |
|---|---|---|
| Scan | Find classes via annotations | Class.forName |
| Register | Store metadata | none |
| Instantiate | Create objects | Constructor.newInstance() |
| Inject | Wire dependencies | Field.set() |
| Initialize | Run init methods | Method.invoke() |
| Active | Ready for business | none |
| Shutdown | Cleanup hooks | Method.invoke() |
πͺ Core takeaway¶
Springβs startup is a grand choreography of reflection β loading, wiring, and awakening beans into a self-managing ecosystem. Once ready, reflection steps offstage and your code runs as pure Java.