MindIQ Academy

02 - Dependency Injection and Beans Notes

A beginner-to-advanced guide to dependency injection, bean scopes, bean lifecycle, autowiring, qualifiers, and Spring container internals for Spring Professional Certification candidates. Covers Spring Framework 6 and Spring Boot 3 concepts.


Table of Contents

  1. Big Picture
  2. Dependency Injection
  3. Spring Bean Basics
  4. Spring Container
  5. Constructor Injection
  6. Setter Injection
  7. Field Injection
  8. Injection Type Comparison
  9. Autowiring Resolution
  10. Multiple Bean Candidates
  11. Qualifiers
  12. @Primary
  13. Bean Scopes
  14. Singleton Scope
  15. Prototype Scope
  16. Singleton Bean Depending on Prototype Bean
  17. Web Scopes
  18. Bean Lifecycle
  19. Lifecycle Diagram
  20. Initialization Callbacks
  21. Destruction Callbacks
  22. Lifecycle Method Order
  23. BeanPostProcessor
  24. Circular Dependencies
  25. Practical Mini Application
  26. Advanced Notes
  27. Certification Traps
  28. Interview Questions and Answers
  29. Quick Revision Notes
  30. One-Page Cheat Sheet

1. Big Picture

Spring is built around the IoC container.

IoC means Inversion of Control: instead of your code creating and wiring objects manually, Spring creates objects, connects dependencies, manages lifecycle callbacks, and provides ready-to-use instances.

In Spring, managed objects are called beans.

Without Spring

OrderService
    |
    | creates manually
    v
JdbcOrderRepository


With Spring

Spring Container
    |
    | creates and wires
    v
OrderService ---> OrderRepository

2. Dependency Injection

Dependency Injection is the process where required dependencies are provided to a class from outside instead of being created inside the class.

2.1 Problem Without Dependency Injection

public class OrderService {

    private final OrderRepository orderRepository = new JdbcOrderRepository();

    public void placeOrder(Order order) {
        orderRepository.save(order);
    }
}

Problems:

  • OrderService is tightly coupled to JdbcOrderRepository.
  • Testing is difficult because a mock repository cannot be easily supplied.
  • Replacing JDBC with JPA, MongoDB, or an external API requires changing service code.

2.2 Solution With Dependency Injection

public interface OrderRepository {
    void save(Order order);
}
@Repository
public class JdbcOrderRepository implements OrderRepository {

    @Override
    public void save(Order order) {
        System.out.println("Saving order using JDBC");
    }
}
@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void placeOrder(Order order) {
        orderRepository.save(order);
    }
}

Now OrderService depends on the abstraction OrderRepository, not a concrete implementation.

3. Spring Bean Basics

A bean is an object managed by the Spring container.

Common ways to define beans:

  • Stereotype annotations: @Component, @Service, @Repository, @Controller
  • Java configuration: @Bean
  • XML configuration, mostly legacy

3.1 Component-Based Bean

@Component
public class EmailNotificationService {

    public void send(String message) {
        System.out.println("Email sent: " + message);
    }
}

3.2 Java Configuration Bean

@Configuration
public class AppConfig {

    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }
}

The method name clock becomes the bean name unless explicitly changed.

@Bean("utcClock")
public Clock clock() {
    return Clock.systemUTC();
}

4. Spring Container

The container:

  • Scans bean definitions.
  • Creates bean instances.
  • Injects dependencies.
  • Applies bean lifecycle callbacks.
  • Manages bean scopes.
  • Destroys beans when appropriate.
Application starts
       |
       v
Spring scans configuration
       |
       v
Bean definitions registered
       |
       v
Beans instantiated
       |
       v
Dependencies injected
       |
       v
Lifecycle callbacks executed
       |
       v
Application uses beans
       |
       v
Container shutdown

5. Constructor Injection

Constructor injection provides dependencies through a class constructor.

@Service
public class PaymentService {

    private final PaymentGateway paymentGateway;

    public PaymentService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public void pay(BigDecimal amount) {
        paymentGateway.charge(amount);
    }
}

Why Constructor Injection Is Preferred

  • Dependencies are mandatory.
  • Object is created in a valid state.
  • Supports immutability with final fields.
  • Easier to test.
  • Avoids hidden dependencies.
  • Detects many circular dependencies early.

Single Constructor Rule

In modern Spring, if a bean has only one constructor, @Autowired is optional.

@Service
public class ReportService {

    private final ReportRepository repository;

    public ReportService(ReportRepository repository) {
        this.repository = repository;
    }
}

This works without @Autowired.

Multiple Constructors

If there are multiple constructors, Spring needs to know which constructor to use.

@Service
public class ReportService {

    private final ReportRepository repository;
    private final AuditService auditService;

    public ReportService(ReportRepository repository) {
        this.repository = repository;
        this.auditService = null;
    }

    @Autowired
    public ReportService(ReportRepository repository, AuditService auditService) {
        this.repository = repository;
        this.auditService = auditService;
    }
}

6. Setter Injection

Setter injection provides dependencies using setter methods.

@Service
public class InvoiceService {

    private TaxService taxService;

    @Autowired
    public void setTaxService(TaxService taxService) {
        this.taxService = taxService;
    }
}

When Setter Injection Is Useful

  • Optional dependencies.
  • Reconfigurable dependencies.
  • Framework integration requiring no-argument constructors.
  • Breaking certain circular dependencies, although this should be a last resort.

Risk of Setter Injection

The object can exist in an incomplete state if a required dependency is not set.

public class InvoiceService {

    private TaxService taxService;

    public BigDecimal total(Invoice invoice) {
        return taxService.calculate(invoice);
    }
}

If taxService is not injected, this causes a NullPointerException.

7. Field Injection

Field injection injects dependencies directly into fields.

@Service
public class CustomerService {

    @Autowired
    private CustomerRepository customerRepository;
}

Field injection works, but it is usually discouraged.

Problems:

  • Harder to test without Spring.
  • Cannot use final.
  • Dependencies are hidden.
  • Object can be constructed manually in an invalid state.
  • Reflection is required.

Certification trap: field injection is valid Spring, but constructor injection is generally preferred.

8. Injection Type Comparison

Injection TypeBest ForProsCons
Constructor injectionRequired dependenciesImmutable, testable, safeCan expose too many dependencies
Setter injectionOptional dependenciesFlexibleObject can be partially initialized
Field injectionSimple demos, legacy codeConciseHidden dependencies, harder testing

9. Autowiring Resolution

When Spring sees a dependency, it resolves it using type information first.

@Service
public class CheckoutService {

    private final PaymentProcessor paymentProcessor;

    public CheckoutService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
}

If there is exactly one PaymentProcessor bean, Spring injects it.

public interface PaymentProcessor {
    void process(BigDecimal amount);
}
@Component
public class CardPaymentProcessor implements PaymentProcessor {

    @Override
    public void process(BigDecimal amount) {
        System.out.println("Card payment");
    }
}

10. Multiple Bean Candidates

If multiple beans of the same type exist, Spring cannot choose automatically.

@Component
public class CardPaymentProcessor implements PaymentProcessor {
}
@Component
public class UpiPaymentProcessor implements PaymentProcessor {
}
@Service
public class CheckoutService {

    public CheckoutService(PaymentProcessor paymentProcessor) {
    }
}

This fails with NoUniqueBeanDefinitionException.

11. Qualifiers

@Qualifier tells Spring exactly which bean to inject.

@Service
public class CheckoutService {

    private final PaymentProcessor paymentProcessor;

    public CheckoutService(
            @Qualifier("cardPaymentProcessor") PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
}

By default, component bean names are derived from class names with the first character lowercased:

CardPaymentProcessor -> cardPaymentProcessor
UpiPaymentProcessor  -> upiPaymentProcessor

Custom Bean Name

@Component("cardProcessor")
public class CardPaymentProcessor implements PaymentProcessor {
}
public CheckoutService(@Qualifier("cardProcessor") PaymentProcessor paymentProcessor) {
    this.paymentProcessor = paymentProcessor;
}

Qualifier With @Bean

@Configuration
public class PaymentConfig {

    @Bean
    public PaymentProcessor cardProcessor() {
        return new CardPaymentProcessor();
    }

    @Bean
    public PaymentProcessor upiProcessor() {
        return new UpiPaymentProcessor();
    }
}
public CheckoutService(@Qualifier("upiProcessor") PaymentProcessor paymentProcessor) {
    this.paymentProcessor = paymentProcessor;
}

12. @Primary

@Primary marks one bean as the default candidate when multiple beans match by type.

@Component
@Primary
public class CardPaymentProcessor implements PaymentProcessor {
}
@Component
public class UpiPaymentProcessor implements PaymentProcessor {
}

Now Spring injects CardPaymentProcessor when no qualifier is provided.

@Qualifier Beats @Primary

public CheckoutService(@Qualifier("upiPaymentProcessor") PaymentProcessor paymentProcessor) {
    this.paymentProcessor = paymentProcessor;
}

Even if CardPaymentProcessor is @Primary, the qualifier selects UpiPaymentProcessor.

13. Bean Scopes

Bean scope controls how many instances Spring creates and how long they live.

ScopeInstancesAvailable InDescription
singletonOne per Spring containerAll Spring appsDefault scope
prototypeNew instance per request from containerAll Spring appsContainer creates but does not fully manage destruction
requestOne per HTTP requestWeb appsValid during a web request
sessionOne per HTTP sessionWeb appsValid during an HTTP session
applicationOne per ServletContextWeb appsShared across servlet application
websocketOne per WebSocket sessionWeb appsValid during WebSocket session

14. Singleton Scope

Singleton is the default Spring bean scope.

@Service
public class CurrencyService {
}

Equivalent to:

@Service
@Scope("singleton")
public class CurrencyService {
}
Spring Container
    |
    +-- currencyService instance 1
    +-- orderService uses same currencyService
    +-- invoiceService uses same currencyService

Important: Spring singleton is one instance per Spring container, not necessarily one instance per JVM.

15. Prototype Scope

Prototype scope creates a new bean instance every time the bean is requested from the container.

@Component
@Scope("prototype")
public class TrackingIdGenerator {

    private final UUID id = UUID.randomUUID();

    public UUID getId() {
        return id;
    }
}
TrackingIdGenerator first = context.getBean(TrackingIdGenerator.class);
TrackingIdGenerator second = context.getBean(TrackingIdGenerator.class);

System.out.println(first == second); // false

Certification trap: Spring calls initialization callbacks for prototype beans, but it does not manage their full destruction lifecycle.

16. Singleton Bean Depending on Prototype Bean

This is a common trap.

@Service
public class ReportService {

    private final TrackingIdGenerator generator;

    public ReportService(TrackingIdGenerator generator) {
        this.generator = generator;
    }
}

If ReportService is singleton and TrackingIdGenerator is prototype, the prototype dependency is injected only once when the singleton is created.

Container startup
       |
       v
Creates singleton ReportService
       |
       v
Injects one prototype TrackingIdGenerator
       |
       v
Same generator reused inside ReportService

Solution 1: ObjectProvider

@Service
public class ReportService {

    private final ObjectProvider<TrackingIdGenerator> generatorProvider;

    public ReportService(ObjectProvider<TrackingIdGenerator> generatorProvider) {
        this.generatorProvider = generatorProvider;
    }

    public void createReport() {
        TrackingIdGenerator generator = generatorProvider.getObject();
        System.out.println(generator.getId());
    }
}

Solution 2: @Lookup

@Service
public abstract class ReportService {

    public void createReport() {
        TrackingIdGenerator generator = trackingIdGenerator();
        System.out.println(generator.getId());
    }

    @Lookup
    protected abstract TrackingIdGenerator trackingIdGenerator();
}

17. Web Scopes

Request Scope

@Component
@RequestScope
public class RequestContext {

    private String correlationId;

    public String getCorrelationId() {
        return correlationId;
    }

    public void setCorrelationId(String correlationId) {
        this.correlationId = correlationId;
    }
}

One instance is created for each HTTP request.

Session Scope

@Component
@SessionScope
public class ShoppingCart {

    private final List<String> items = new ArrayList<>();

    public void add(String item) {
        items.add(item);
    }
}

One instance is created for each HTTP session.

Scoped Proxy

A singleton bean cannot directly hold a normal request-scoped bean because the request bean does not exist at application startup.

@Component
@RequestScope
public class RequestContext {
}
@Service
public class AuditService {

    private final RequestContext requestContext;

    public AuditService(RequestContext requestContext) {
        this.requestContext = requestContext;
    }
}

Spring may need a proxy:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
}
Singleton AuditService
       |
       v
RequestContext proxy
       |
       v
Actual RequestContext for current HTTP request

18. Bean Lifecycle

Bean lifecycle means the sequence of steps from bean creation to destruction.

1. Bean definition loaded
2. Bean instantiated
3. Dependencies injected
4. Aware callbacks invoked
5. BeanPostProcessor before initialization
6. Initialization callbacks
7. BeanPostProcessor after initialization
8. Bean ready for use
9. Destruction callbacks on shutdown

19. Lifecycle Diagram

Spring Container
       |
       v
Instantiate bean
       |
       v
Populate properties / inject dependencies
       |
       v
BeanNameAware / BeanFactoryAware / ApplicationContextAware
       |
       v
BeanPostProcessor before initialization
       |
       v
@PostConstruct
       |
       v
InitializingBean.afterPropertiesSet()
       |
       v
custom init-method
       |
       v
BeanPostProcessor after initialization
       |
       v
Bean ready
       |
       v
@PreDestroy
       |
       v
DisposableBean.destroy()
       |
       v
custom destroy-method

20. Initialization Callbacks

@PostConstruct

@Component
public class CacheLoader {

    @PostConstruct
    public void loadCache() {
        System.out.println("Loading cache");
    }
}

@PostConstruct runs after dependency injection is complete.

InitializingBean

@Component
public class CacheLoader implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        System.out.println("Loading cache");
    }
}

This couples the class to Spring.

Custom Init Method

@Configuration
public class CacheConfig {

    @Bean(initMethod = "start")
    public CacheClient cacheClient() {
        return new CacheClient();
    }
}
public class CacheClient {

    public void start() {
        System.out.println("Starting cache client");
    }
}

21. Destruction Callbacks

@PreDestroy

@Component
public class ConnectionPool {

    @PreDestroy
    public void close() {
        System.out.println("Closing connections");
    }
}

DisposableBean

@Component
public class ConnectionPool implements DisposableBean {

    @Override
    public void destroy() {
        System.out.println("Closing connections");
    }
}

Custom Destroy Method

@Configuration
public class ConnectionConfig {

    @Bean(destroyMethod = "shutdown")
    public ConnectionPool connectionPool() {
        return new ConnectionPool();
    }
}

22. Lifecycle Method Order

If several initialization mechanisms are present, the order is:

1. @PostConstruct
2. InitializingBean.afterPropertiesSet()
3. custom init-method

For destruction:

1. @PreDestroy
2. DisposableBean.destroy()
3. custom destroy-method

23. BeanPostProcessor

BeanPostProcessor allows custom logic before and after bean initialization.

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("Before init: " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("After init: " + beanName);
        return bean;
    }
}

Common real-world uses:

  • Creating proxies.
  • Processing annotations.
  • Adding metrics or logging.
  • Supporting framework features such as @Autowired.

24. Circular Dependencies

A circular dependency occurs when two or more beans depend on each other.

ServiceA ---> ServiceB
   ^           |
   |           v
   +-----------+

24.1 Constructor-Based Circular Dependency

@Service
public class ServiceA {

    public ServiceA(ServiceB serviceB) {
    }
}
@Service
public class ServiceB {

    public ServiceB(ServiceA serviceA) {
    }
}

This usually fails because neither bean can be created first.

Create ServiceA
       |
       v
Needs ServiceB
       |
       v
Create ServiceB
       |
       v
Needs ServiceA
       |
       v
Failure

24.2 Setter-Based Circular Dependency

Setter injection may allow some circular dependencies because Spring can instantiate objects first and inject dependencies later.

@Service
public class ServiceA {

    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
@Service
public class ServiceB {

    private ServiceA serviceA;

    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

This is not a design recommendation. It is a workaround.

24.3 Better Fix: Redesign Responsibilities

Bad design:

OrderService <----> PaymentService

Better design:

OrderService ---> PaymentService
       |
       v
OrderStatusUpdater

Or use events:

OrderService publishes OrderPlacedEvent
       |
       v
PaymentListener handles payment
public record OrderPlacedEvent(Long orderId) {
}
@Service
public class OrderService {

    private final ApplicationEventPublisher publisher;

    public OrderService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void placeOrder(Long orderId) {
        publisher.publishEvent(new OrderPlacedEvent(orderId));
    }
}
@Component
public class PaymentListener {

    @EventListener
    public void onOrderPlaced(OrderPlacedEvent event) {
        System.out.println("Taking payment for order " + event.orderId());
    }
}

24.4 @Lazy for Circular Dependencies

@Lazy can delay dependency resolution.

@Service
public class ServiceA {

    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

Use this carefully. It can hide a design problem.

25. Practical Mini Application

Requirement

Create an order checkout flow with:

  • CheckoutService
  • Two payment processors
  • Constructor injection
  • Qualifier selection
  • Lifecycle callback

Code

public interface PaymentProcessor {
    void process(BigDecimal amount);
}
@Component("cardProcessor")
public class CardPaymentProcessor implements PaymentProcessor {

    @Override
    public void process(BigDecimal amount) {
        System.out.println("Paid by card: " + amount);
    }
}
@Component("upiProcessor")
public class UpiPaymentProcessor implements PaymentProcessor {

    @PostConstruct
    public void init() {
        System.out.println("UPI processor initialized");
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("UPI processor shutting down");
    }

    @Override
    public void process(BigDecimal amount) {
        System.out.println("Paid by UPI: " + amount);
    }
}
@Service
public class CheckoutService {

    private final PaymentProcessor paymentProcessor;

    public CheckoutService(@Qualifier("upiProcessor") PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public void checkout(BigDecimal amount) {
        paymentProcessor.process(amount);
    }
}
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        CheckoutService checkoutService = context.getBean(CheckoutService.class);
        checkoutService.checkout(new BigDecimal("999.00"));
        context.close();
    }
}

Expected output:

UPI processor initialized
Paid by UPI: 999.00
UPI processor shutting down

26. Advanced Notes

26.1 Dependency Injection Is Not Only @Autowired

Spring can inject dependencies through:

  • Constructor parameters
  • Setter methods
  • Fields
  • @Bean method parameters
@Configuration
public class AppConfig {

    @Bean
    public CheckoutService checkoutService(PaymentProcessor paymentProcessor) {
        return new CheckoutService(paymentProcessor);
    }
}

26.2 Optional Dependencies

Using Optional:

@Service
public class NotificationService {

    public NotificationService(Optional<SmsClient> smsClient) {
        smsClient.ifPresent(client -> System.out.println("SMS enabled"));
    }
}

Using ObjectProvider:

@Service
public class NotificationService {

    private final ObjectProvider<SmsClient> smsClientProvider;

    public NotificationService(ObjectProvider<SmsClient> smsClientProvider) {
        this.smsClientProvider = smsClientProvider;
    }

    public void notifyUser(String message) {
        SmsClient smsClient = smsClientProvider.getIfAvailable();
        if (smsClient != null) {
            smsClient.send(message);
        }
    }
}

26.3 Injecting All Beans of a Type

@Service
public class PaymentRouter {

    private final List<PaymentProcessor> processors;

    public PaymentRouter(List<PaymentProcessor> processors) {
        this.processors = processors;
    }
}

Spring injects all beans implementing PaymentProcessor.

26.4 Injecting a Map of Beans

@Service
public class PaymentRouter {

    private final Map<String, PaymentProcessor> processors;

    public PaymentRouter(Map<String, PaymentProcessor> processors) {
        this.processors = processors;
    }

    public void pay(String type, BigDecimal amount) {
        processors.get(type).process(amount);
    }
}

The map keys are bean names.

27. Certification Traps

TrapCorrect Understanding
Spring singleton means one object in the JVM.It means one instance per Spring container.
Prototype beans are destroyed automatically by Spring.Spring initializes prototypes but does not fully manage their destruction.
@Autowired is always required on constructors.It is optional when there is only one constructor.
Field injection is invalid.It is valid but usually discouraged.
@Qualifier and @Primary have equal priority.@Qualifier is more specific and wins.
A prototype bean injected into a singleton gives a new instance every method call.It gives one prototype instance at singleton creation time unless provider or lookup is used.
@PostConstruct runs before dependency injection.It runs after dependency injection.
Constructor circular dependencies are easy for Spring to resolve.They usually fail because both beans need each other during construction.
@Service, @Repository, and @Controller are unrelated to @Component.They are specialized forms of @Component.
Bean name and class name are always identical.Default bean name usually starts with lowercase class name.

28. Interview Questions and Answers

1. What is Dependency Injection?

Dependency Injection is a design pattern where an object's dependencies are supplied from outside instead of being created inside the object. In Spring, the container performs this wiring.

2. Why is constructor injection preferred?

It makes dependencies explicit, supports immutability, ensures required dependencies are available at creation time, and improves testability.

3. What is a Spring bean?

A Spring bean is an object created, configured, and managed by the Spring IoC container.

4. What is the default bean scope in Spring?

The default scope is singleton.

5. Is Spring singleton the same as the Singleton design pattern?

No. The Singleton design pattern usually means one instance per classloader or JVM. Spring singleton means one instance per Spring container.

6. What happens when multiple beans match the same dependency type?

Spring throws NoUniqueBeanDefinitionException unless one bean is selected using @Primary, @Qualifier, bean name matching, or another resolution mechanism.

7. What is the difference between @Primary and @Qualifier?

@Primary marks a default bean. @Qualifier explicitly selects a bean and has higher priority.

8. What is a circular dependency?

A circular dependency occurs when two or more beans depend on each other directly or indirectly.

9. How can circular dependencies be fixed?

The best fix is redesigning responsibilities. Other options include using events, splitting services, setter injection, ObjectProvider, or @Lazy.

10. What is the difference between singleton and prototype scope?

Singleton creates one bean instance per container. Prototype creates a new instance whenever requested from the container.

11. Does Spring call destroy methods on prototype beans?

Generally no. Spring creates and initializes prototype beans but does not manage their complete destruction lifecycle.

12. What is BeanPostProcessor?

It is an extension point that allows custom logic before and after bean initialization.

13. When does @PostConstruct run?

It runs after the bean is instantiated and dependencies are injected, but before the bean is used.

14. What is setter injection best suited for?

Setter injection is best suited for optional or reconfigurable dependencies.

15. How do you inject all implementations of an interface?

Use List<InterfaceType> or Map<String, InterfaceType> in the constructor.

29. Quick Revision Notes

  • Spring IoC container creates and manages beans.
  • Dependency Injection reduces tight coupling.
  • Constructor injection is preferred for required dependencies.
  • Setter injection is useful for optional dependencies.
  • Field injection is valid but discouraged.
  • singleton is the default bean scope.
  • Spring singleton means one instance per container.
  • Prototype scope creates a new instance when requested from the container.
  • Prototype beans injected into singleton beans are not refreshed automatically.
  • Use ObjectProvider or @Lookup for fresh prototype instances inside singletons.
  • @Qualifier selects a specific bean.
  • @Primary selects the default bean when multiple candidates exist.
  • @Qualifier overrides @Primary.
  • @PostConstruct runs after dependency injection.
  • @PreDestroy runs before bean destruction.
  • Constructor circular dependencies usually fail.
  • Circular dependencies often indicate poor design.
  • Prefer redesign, domain events, or responsibility separation over circular wiring.

30. One-Page Cheat Sheet

Core Terms

TermMeaning
IoCControl of object creation is moved to the Spring container
DIDependencies are supplied from outside
BeanObject managed by Spring
ScopeLifecycle and instance creation rule for a bean
AutowiringSpring automatically resolves and injects dependencies

Injection Choices

Use CaseRecommended Injection
Required dependencyConstructor injection
Optional dependencySetter injection or ObjectProvider
Quick demo or legacy codeField injection
Multiple implementationsConstructor injection with @Qualifier

Bean Scopes

ScopeInstance Rule
singletonOne instance per Spring container
prototypeNew instance per container request
requestOne instance per HTTP request
sessionOne instance per HTTP session
applicationOne instance per servlet context
websocketOne instance per WebSocket session

Candidate Selection

Spring resolves by type
       |
       v
One candidate? inject it
       |
       v
Multiple candidates?
       |
       +-- @Qualifier present? use it
       |
       +-- @Primary present? use it
       |
       +-- parameter name matches bean name? may use it
       |
       +-- otherwise fail

Lifecycle Order

Instantiate
Inject dependencies
Aware callbacks
BeanPostProcessor before init
@PostConstruct
InitializingBean.afterPropertiesSet()
custom init-method
BeanPostProcessor after init
Bean ready
@PreDestroy
DisposableBean.destroy()
custom destroy-method

Must-Remember Exam Points

  • @Autowired is optional on a single constructor.
  • @Qualifier is more specific than @Primary.
  • @PostConstruct happens after dependency injection.
  • Prototype destruction is not fully managed by Spring.
  • Constructor injection exposes circular dependencies early.
  • Setter injection can sometimes resolve circular dependencies but is not the preferred design fix.
  • A singleton bean receives a prototype dependency only once unless lookup or provider-based access is used.