How Dependency Injection Works in Spring Boot Using @Autowired

Dependency Injection (DI) is a fundamental concept in Spring Boot that facilitates the management of dependencies between various components in your application. Spring Boot primarily uses the @Autowired annotation to enable automatic wiring of beans. Below, I’ll explain how Dependency Injection works using @Autowired in Spring Boot, step by step.

What is Dependency Injection?

Instead of creating dependencies manually within components, Spring’s IoC (Inversion of Control) container injects dependencies into an object automatically. This reduces tight coupling and makes code more reusable and flexible.

How Does @Autowired Work?

@Autowired is used to mark a dependency that Spring should handle automatically. It tells Spring, “Find the appropriate bean in the IoC container and inject it here.”

Here’s how it works:

  1. Mark the class as a Spring Bean using @Component, @Service, @Repository, or by defining it explicitly via @Bean in a configuration class.
  2. Use the @Autowired Annotation to inject a required dependency into a field, constructor, or setter method.

Approaches to Using @Autowired

1. Field Injection

This is the simplest form of dependency injection where the field is annotated with @Autowired.

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class CarService {

    @Autowired
    private Engine engine;

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Quick and readable.
  • Cons: Field injection is less testable and not recommended for complex applications because the dependency is hard to mock in unit tests.

2. Constructor Injection (Recommended)

Here, dependencies are injected via the constructor. This is the most preferred approach as it promotes immutability and easy testing.

import org.springframework.stereotype.Component;

@Component
public class CarService {

    private final Engine engine;

    @Autowired // Optional in Spring Boot since 4.3+
    public CarService(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Cleaner, better for unit testing, adheres to immutability principles.
  • Cons: Requires explicit constructors, but modern tools (e.g., Lombok) simplify this.

3. Setter Injection

Dependencies are injected via setter methods. This is helpful when a bean property is optional and not required during object creation.

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class CarService {

    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Useful when you want to inject optional dependencies.
  • Cons: Makes the class mutable and harder to test.

Behind the Scenes

  1. Bean Discovery: The Spring IoC container detects beans annotated with @Component, @Service, @Repository, @Controller, or those defined explicitly in configuration classes.
  2. Dependency Resolution: When @Autowired is detected on a field, constructor, or setter, Spring scans its ApplicationContext to find a matching bean (by type).
  3. Injection: The resolved bean (dependency) is injected into the target where @Autowired is present.

Handling Ambiguity

If multiple beans of the same type are defined, Spring would throw a NoUniqueBeanDefinitionException. You can resolve this by:

  • Using @Qualifier:
@Autowired
@Qualifier("specificEngine")
private Engine engine;
  • Marking a bean as a primary candidate:
@Primary
@Component
public class Engine { ... }

Example: A Working Spring Boot Application

Here’s a complete example of how @Autowired can be used in a Spring Boot application.

Application Entry Point

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootExampleApplication.class, args);
    }
}

Service & Dependency

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String getGreeting() {
        return "Hello, Dependency Injection!";
    }
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.getGreeting();
    }
}

Run the application, and visiting http://localhost:8080/greet will give you the message:
Hello, Dependency Injection!

Best Practices

  1. Use Constructor Injection for mandatory dependencies.
  2. Avoid Field Injection, except for small applications or testing purposes.
  3. Annotate beans properly using stereotypes like @Service, @Component, etc.
  4. For optional beans, use @Autowired(required = false) or setter injection.
  5. Handle bean ambiguity with @Qualifier or @Primary.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.