DEV Community

Sudhakar V
Sudhakar V

Posted on

Dependency Injection

Dependency Injection (DI) is a design pattern and a core concept of Inversion of Control (IoC) whereby an object’s dependencies (the other objects it works with) are supplied (“injected”) by an external entity rather than the object creating them itself. This leads to more modular, testable, and maintainable code.


1. Why Use Dependency Injection?

  • Loose Coupling Objects depend on interfaces or abstractions, not concrete implementations. You can swap implementations without changing the dependent class.
  • Testability You can inject mock or stub implementations in unit tests easily.
  • Separation of Concerns Classes focus on their own logic; the wiring of collaborators is handled elsewhere.
  • Configurability You can change which implementation is used via configuration (XML, annotations, properties) without code changes.

2. Types of Dependency Injection

Injection Style How It Works Pros / Cons
Constructor Injection Dependencies are provided through a class constructor. ✔ Makes dependencies explicit and immutable.
                                                                                                                Can’t create object without its dependencies. |
Enter fullscreen mode Exit fullscreen mode

| Setter Injection | Dependencies are provided through setter methods (JavaBeans style). | ✔ Good for optional dependencies.
✖ Dependent object can exist in an incomplete state. |
| Field Injection | Dependencies are injected directly into fields (usually via reflection). | ✔ Concise, minimal boilerplate.
✖ Harder to test; hides dependencies in fields. |


3. Constructor Injection

@Component
public class OrderService {
  private final PaymentProcessor processor;

  // Spring sees @Autowired (or single constructor) and injects the bean
  @Autowired
  public OrderService(PaymentProcessor processor) {
    this.processor = processor;
  }

  public void placeOrder(Order o) {
    processor.process(o.getPaymentDetails());
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Advantages

    • All required dependencies are enforced at creation time.
    • Fields can be declared final, ensuring immutability.
  • When to Use

    • For required dependencies that every instance must have.

4. Setter Injection

@Component
public class ReportGenerator {
  private DataSource dataSource;

  @Autowired
  public void setDataSource(DataSource ds) {
    this.dataSource = ds;
  }

  public void generate() {
    // use dataSource
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Advantages

    • Allows optional or changeable dependencies.
  • Drawbacks

    • Object may be in an unusable state until all setters have been called.

5. Field Injection

@Component
public class NotificationService {
  @Autowired
  private EmailClient emailClient;

  public void notify(User u) {
    emailClient.send(u.getEmail(), "Hello!");
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Advantages

    • Very concise, no constructor or setter boilerplate.
  • Drawbacks

    • Harder to write unit tests (you need reflection or Spring test support).
    • Dependencies aren’t obvious from the class API.

6. How Spring Resolves & Injects

  1. Bean Definition Spring scans your @Configuration and @Component classes (or reads XML) and builds bean definitions.
  2. Dependency Graph It analyzes constructors, setters, and fields annotated with @Autowired (or other injection annotations like @Inject or @Resource) to figure out who depends on whom.
  3. Injection During container startup, it instantiates beans in the correct order, injecting each dependency from the pool of managed beans.
  4. Qualification If multiple candidates exist for a dependency, you can use @Qualifier or bean names to resolve ambiguity.

7. Advanced Injection Features

  • Optional Dependencies
  @Autowired(required = false)
  private AuditService auditService;
Enter fullscreen mode Exit fullscreen mode
  • Injecting Collections
  @Autowired
  private List<NotificationHandler> handlers;
Enter fullscreen mode Exit fullscreen mode
  • Custom Qualifiers
  @Qualifier("fastPayment")
  @Autowired
  private PaymentProcessor processor;
Enter fullscreen mode Exit fullscreen mode
  • Environment & Properties Inject configuration values with @Value("${app.timeout:30}").

8. Putting It All Together: Example Application Context

@Configuration
@ComponentScan("com.example.shop")
public class ShopConfig { }

@Service
public class CheckoutService {
  private final Inventory inventory;
  private final PaymentProcessor payment;

  public CheckoutService(Inventory inventory,
                         @Qualifier("stripe") PaymentProcessor payment) {
    this.inventory = inventory;
    this.payment   = payment;
  }
  // ...
}

@Component
public class StripeProcessor implements PaymentProcessor { /* ... */ }

@Component
public class PaypalProcessor implements PaymentProcessor { /* ... */ }
Enter fullscreen mode Exit fullscreen mode
  • Spring will scan, create beans for StripeProcessor and PaypalProcessor, then inject the one qualified as "stripe" into CheckoutService.

✅ Recap

  • Dependency Injection shifts the responsibility of providing dependencies away from your code to the Spring container.
  • Choose constructor injection for required, immutable dependencies; setter injection for optional ones; and field injection sparingly.
  • Use @Autowired, @Qualifier, and other annotations to guide Spring’s injection process.
  • DI in Spring leads to cleaner, more testable, and easier-to-maintain applications.

Top comments (0)