IoC and Dependency Injection
Inversion of Control means the framework creates and wires objects instead of your code manually calling new everywhere. Dependency Injection is the main technique Spring uses to provide dependencies to a class.
Bean Lifecycle
A Spring bean is created, dependencies are injected, initialization callbacks run, the bean is used, and destroy callbacks run when the context closes.
Common lifecycle hooks:
| Hook | Purpose |
|---|---|
| Constructor | Create valid required state |
@PostConstruct | Run setup after dependency injection |
initMethod | XML/Java config initialization hook |
@PreDestroy | Cleanup before destruction |
destroyMethod | Configured cleanup hook |
Bean Scopes
Singleton is the default: one bean per Spring container. Prototype creates a new instance each request from the container. Web scopes include request and session.
Interview Framing
Constructor injection is usually preferred for required dependencies because it supports immutability, easier testing, and fail-fast object creation.
Spring Code Examples
Constructor Injection
@Service
public class OrderService {
private final PaymentClient paymentClient;
private final OrderRepository orderRepository;
public OrderService(PaymentClient paymentClient,
OrderRepository orderRepository) {
this.paymentClient = paymentClient;
this.orderRepository = orderRepository;
}
public void placeOrder(Order order) {
paymentClient.charge(order.total());
orderRepository.save(order);
}
}
Bean Lifecycle Hooks
@Component
public class CacheWarmup {
@PostConstruct
public void init() {
System.out.println("Load cache after dependencies are injected");
}
@PreDestroy
public void shutdown() {
System.out.println("Flush metrics before bean is destroyed");
}
}
Interview Scenario Practice
Scenario 1: Required Dependencies
Scenario: A service cannot work without its repository and payment client.
Strong answer: Prefer constructor injection. Required dependencies become visible in the constructor, can be marked final, and fail fast if missing.
Why it works: Constructor injection improves testability and makes the class contract obvious.
Common mistake: Using field injection everywhere because it looks shorter. It hides required dependencies and makes unit tests clumsier.
Scenario 2: Cache Warmup
Scenario: You need to load reference data after Spring has injected all dependencies.
Strong answer: Use @PostConstruct for initialization that needs injected collaborators, or a dedicated startup runner for larger boot workflows.
Why it works: The bean is already constructed and dependencies are available.
Common mistake: Doing heavy dependency-based work inside the constructor before Spring has fully wired the object.
Scenario 3: User Data in Singleton Bean
Scenario: A singleton-scoped service stores per-request user information in a field.
Strong answer: Avoid mutable request-specific state in singleton beans. Keep state local to methods, use request scope where appropriate, or pass context explicitly.
Why it works: Singleton beans are shared, so mutable fields can leak values between requests and threads.
Common mistake: Assuming a Spring singleton means one instance per user. It means one instance per Spring container.