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. |
| 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());
}
}
-
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
}
}
-
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!");
}
}
-
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
-
Bean Definition
Spring scans your
@Configuration
and@Component
classes (or reads XML) and builds bean definitions. -
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. - Injection During container startup, it instantiates beans in the correct order, injecting each dependency from the pool of managed beans.
-
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;
- Injecting Collections
@Autowired
private List<NotificationHandler> handlers;
- Custom Qualifiers
@Qualifier("fastPayment")
@Autowired
private PaymentProcessor processor;
-
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 { /* ... */ }
- Spring will scan, create beans for
StripeProcessor
andPaypalProcessor
, then inject the one qualified as"stripe"
intoCheckoutService
.
✅ 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)