🌱 Spring Boot Q1 / 50

How does dependency injection work internally in Spring Boot?

AI-Powered Answer ✓ Answered

Spring Boot leverages the Spring Framework's Inversion of Control (IoC) container to manage and inject dependencies into application components. This process involves several internal mechanisms, from bean definition to lifecycle management and post-processing.

1. The Inversion of Control (IoC) Container

At the heart of Spring's dependency injection is the IoC container, typically implemented by an ApplicationContext (e.g., AnnotationConfigApplicationContext in Spring Boot). This container is responsible for instantiating, configuring, and assembling your application's components, known as 'beans'.

When a Spring Boot application starts, SpringApplication.run() is called. This method is responsible for bootstrapping the application, which includes creating and configuring the ApplicationContext. The context then scans for components, registers their definitions, and manages their lifecycle.

2. Bean Definition and Registration

Component Scanning

Spring Boot simplifies bean definition through component scanning. By default, it scans packages rooted at the main application class for classes annotated with stereotypes like @Component, @Service, @Repository, and @Controller. These annotations signal to the IoC container that the class should be managed as a Spring bean.

java
@Service
public class MyServiceImpl implements MyService {
    // ...
}

@Bean Annotation

Alternatively, beans can be explicitly defined using the @Bean annotation within a @Configuration class. This is useful for defining third-party beans or when the bean's instantiation logic is complex.

java
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

BeanDefinition Objects

For every discovered or explicitly defined bean, the ApplicationContext creates a BeanDefinition object. This object holds metadata about the bean, such as its class, scope (singleton, prototype), constructor arguments, property values, and any dependencies it might have. These definitions are stored in the IoC container.

3. Dependency Resolution and Injection

@Autowired Annotation

The primary mechanism for requesting dependency injection is the @Autowired annotation. When Spring encounters @Autowired on a constructor, setter method, or field, it attempts to find a suitable bean of the required type within the ApplicationContext to inject.

java
@Service
public class OrderService {
    private final ProductRepository productRepository;

    @Autowired // Optional for single-constructor injection from Spring 4.3+
    public OrderService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
}

Resolution Strategy

Spring resolves dependencies primarily by type. If multiple beans of the same type are found, it then tries to match by name. If ambiguity still exists, @Qualifier can be used to explicitly specify the bean name, or @Primary can designate a default choice.

4. Bean Instantiation and Wiring

Once a BeanDefinition is processed and its dependencies are resolved, the IoC container instantiates the bean. This involves invoking the constructor (for constructor injection) or setter methods (for setter injection) and passing the resolved dependencies as arguments. Spring manages the entire lifecycle of the bean, including initialization and destruction callbacks.

5. The Role of BeanPostProcessors

A crucial internal component for dependency injection is the BeanPostProcessor interface. These processors allow for custom modification of new bean instances, for example, checking for marker interfaces or wrapping beans with proxies. They execute before and after a bean's initialization callbacks.

AutowiredAnnotationBeanPostProcessor

The AutowiredAnnotationBeanPostProcessor is a core BeanPostProcessor implicitly registered by Spring. Its primary responsibility is to scan beans for @Autowired, @Value, and @Inject annotations (and others like @Resource). When it finds these annotations, it identifies the required dependencies and then injects them into the bean using the information stored in the ApplicationContext.

This post-processor runs *after* a bean has been instantiated but *before* any custom initialization methods (like those annotated with @PostConstruct) are called. It's the mechanism that translates the @Autowired declaration into actual dependency injection.

In summary, Spring Boot's dependency injection relies on the ApplicationContext to manage bean definitions, component scanning to discover beans, @Autowired for requesting dependencies, and BeanPostProcessor implementations (especially AutowiredAnnotationBeanPostProcessor) to perform the actual injection based on resolved types and names.