Core

IoC, DI, Bean Lifecycle, AOP

The fundamentals that underpin everything else. The concepts people half-remember and trip over in interviews.

01
Foundation

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.

Before vs After IoC
Without IoC
With IoC
UserService svc = new UserService( new UserRepository( new DataSource(...)));

You manage every object manually. Change one constructor โ†’ fix every call site.

@Service class UserService { private final UserRepository repo; // Spring injects automatically UserService(UserRepository repo) { this.repo = repo; } }

Spring creates and wires everything. You declare the dependency; the container resolves it.

02
Wiring

Dependency Injection

StyleHowWhen 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
Why constructor injection wins: immutability (final fields), explicit dependencies (all required deps visible in one place), and testability โ€” you can test with plain new, no Spring context needed.
  • @Autowired is 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
03
Lifecycle

Bean Scopes

ScopeInstanceTypical 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
The singleton trap: Injecting a prototype-scoped bean into a singleton doesn't work as expected โ€” the singleton holds one reference forever. The prototype is created once at injection time, never again. Fix: use ApplicationContext.getBean() or @Lookup to get a fresh instance each time.
04
Lifecycle

Bean Lifecycle

The full sequence from birth to destruction:

Bean Lifecycle Rail
Instantiate
Inject Deps
Aware Callbacks
@PostConstruct
In Use
@PreDestroy
Destroyed
  • @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 @Transactional and @Cacheable get their proxy wrappers.
05
Cross-Cutting

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.

TermDefinition
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.

Proxy Interception Flow
Caller SPRING PROXY Before Advice Target Method After / AfterReturning โ†“ โ†“ Response
06
Pitfalls

Common Gotchas

Gotcha 1: Self-invocation bypasses AOP
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.
Gotcha 2: Field injection in tests
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.
Gotcha 3: Singleton + mutable state
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.