IoC, DI, Bean Lifecycle, AOP
The fundamentals that underpin everything else. The concepts people half-remember and trip over in interviews.
Inversion of Control
You describe what you need, the container provides it. You don't create dependencies โ the framework creates them and hands them to you. That's the inversion: control of object creation moves from your code to the framework.
- Testability โ swap real implementations for mocks without changing the class
- Loose coupling โ classes depend on interfaces, not concrete implementations
- No manual wiring โ the container resolves the dependency graph for you
BeanFactory is the low-level container. ApplicationContext extends it and adds
event publishing, i18n, and annotation support. In practice, you always use ApplicationContext
โ just know that BeanFactory exists under it.
You manage every object manually. Change one constructor โ fix every call site.
Spring creates and wires everything. You declare the dependency; the container resolves it.
Dependency Injection
| Style | How | When to Use |
|---|---|---|
| Constructor injection | final field + constructor arg | Always โ preferred |
| Setter injection | @Autowired on setter | Optional dependencies only |
| Field injection | @Autowired on field | Never in production |
final fields), explicit dependencies
(all required deps visible in one place), and testability โ you can test with plain new,
no Spring context needed.
@Autowiredis optional on constructors when there's only one constructor (Spring 4.3+)@Qualifierโ disambiguates when you have two beans of the same type@Primaryโ marks one bean as the default when multiple candidates exist
Bean Scopes
| Scope | Instance | Typical Use |
|---|---|---|
singleton | One per container (default) | Stateless services, repositories |
prototype | New per injection point | Stateful helpers |
request | One per HTTP request | Web: request-scoped data |
session | One per HTTP session | Web: user session state |
application | One per ServletContext | Rarely used |
ApplicationContext.getBean() or @Lookup to get a fresh instance each time.
Bean Lifecycle
The full sequence from birth to destruction:
@PostConstructโ runs after injection. Use for init logic (loading caches, validating config).@PreDestroyโ runs before shutdown. Use for cleanup (closing connections, flushing buffers).BeanPostProcessorโ how Spring itself creates proxies. Runs on every bean. This is how@Transactionaland@Cacheableget their proxy wrappers.
AOP โ Aspect-Oriented Programming
Cross-cutting concerns โ logging, transactions, security โ applied consistently without polluting every business method. You define the logic once; Spring weaves it in via proxies.
| Term | Definition |
|---|---|
| Aspect | The class containing the cross-cutting logic |
| Joinpoint | A point in execution (in Spring: always a method call) |
| Pointcut | The expression that selects which joinpoints to intercept |
| Advice | The code that runs โ Before / After / Around |
@Around is the most powerful โ it wraps the method and controls whether to proceed.
The @Transactional annotation is itself an AOP aspect โ this is why it feels like magic.
Common Gotchas
Calling a
@Transactional method from within the same class doesn't go through
the proxy โ the transaction never starts. The fix: extract the method to a separate bean,
or inject the proxy via self reference.
Field-injected beans require the Spring context to instantiate. Constructor-injected beans can be tested with plain
new. This is the real cost of @Autowired
on fields โ your unit tests become slow integration tests.
Singleton beans are shared across all requests. Storing request-specific state in a field on a singleton bean causes race conditions. Keep singletons stateless โ use method parameters or request-scoped beans for per-request data.