Dependency Injection (DI)
Last updated
Was this helpful?
Last updated
Was this helpful?
Dependency Injection is a specific implementation of IoC where the dependencies of an object are "injected" into it by an external entity, typically the Spring container. Instead of an object creating its dependencies directly, it receives them from an external source. This reduces the coupling between classes and makes them easier to test and maintain. This whole process is also called wiring in Spring and by using annotations it can be done automatically by Spring, referred to as auto-wiring of beans in Spring.
Points of Dependency Injection
Dependency: A dependency is an object that another object depends on to perform its work. Dependencies can be services, data access objects, configuration settings, or any other object that a class needs to function properly.
Dependent Object (or Client): The dependent object is the object that requires a dependency to perform its work. It relies on the dependency to provide certain functionality or services.
Dependency Provider (or Injector): The dependency provider is responsible for providing the required dependencies to the dependent object. In the context of Spring, the dependency provider is typically the Spring IoC container.
Dependencies are provided to the dependent object through its constructor. This is the most common form of dependency injection in Spring.
In this example, UserService
class has a dependency on UserRepository
. The UserRepository
dependency is injected into the UserService
class through its constructor. Constructor Injection promotes immutability and ensures that all required dependencies are provided at the time of object creation.
Always prefer Constructor Injection for better testability, maintainability, and reliability.
Dependencies are provided to the dependent object through setter methods. This allows for more flexibility as dependencies can be changed at runtime.
In this example, UserService
class has a setter method setUserRepository()
to set the UserRepository
dependency. The dependency is injected into the class by calling this setter method. Setter Injection provides flexibility as dependencies can be changed at runtime.
Dependencies are injected directly into the fields of the dependent object. While convenient, field injection is generally discouraged in favor of constructor or setter injection due to concerns about testability and encapsulation.
In this example, UserService
class uses Field Injection to inject the UserRepository
dependency directly into the userRepository
field. The @Autowired
annotation instructs the Spring IoC container to inject the dependency into the field. While convenient, Field Injection is generally discouraged due to following reasons.
Field Injection (@Autowired
on private fields) is widely discouraged in Spring applications due to multiple design and maintainability issues.
Field Injection (@Autowired
on private fields) is widely discouraged in Spring applications due to multiple design and maintainability issues.
How does Field Injection make testing harder?
With Field Injection, dependencies are injected directly into private fields, making it impossible to manually set mocks in unit tests.
Since there are no constructors or setter methods available, the only way to inject mocks is through reflection, which adds unnecessary complexity.
Example: Why is this a problem?
In a unit test, if we try to mock UserRepository
using Mockito:
Why does it fail?
@InjectMocks
only works for Constructor or Setter Injection.
Since userRepository
is private
and lacks a setter or constructor, it remains uninitialized, leading to NullPointerException.
How to fix it?
Use Constructor Injection instead
Now, the test will work correctly:
How does Field Injection violate encapsulation?
In Object-Oriented Programming (OOP), encapsulation means hiding internal implementation details and exposing only necessary functionality.
Field Injection exposes dependencies as class-level fields, breaking encapsulation principles.
Even though fields are marked as private
, Spring still modifies them via reflection, bypassing standard OOP practices.
Example:
This class depends on Spring to inject userRepository
, making it tightly coupled to the framework.
How to fix it?
With Constructor Injection, dependencies remain immutable and are enforced at object creation:
Now, userRepository
cannot be modified after object creation, improving encapsulation.
How does Field Injection hide dependencies?
Dependencies injected via @Autowired
are not visible in the class's public API.
It’s unclear which dependencies the class needs, making it harder to maintain and use.
Example:
A developer looking at this class cannot tell what dependencies are required without inspecting the field or checking the application context.
How to fix it?
Using Constructor Injection makes dependencies explicit:
Now, anyone reading this class knows exactly what it depends on, improving code clarity.
How does Field Injection increase coupling?
@Autowired
makes dependencies hardcoded into the class, making them difficult to replace or swap with alternative implementations.
This goes against the Dependency Inversion Principle (DIP), where high-level modules should not depend on low-level implementations.
Example:
UserService
depends directly on UserRepository
, making it hard to switch to another implementation (e.g., MockUserRepository
for testing).
If we later introduce a caching layer (CachedUserRepository
), we must modify the class.
How to fix it?
Using Constructor Injection with Interfaces allows easy swapping:
Now, UserRepository
can be replaced without modifying UserService
.
A different implementation (e.g., CachedUserRepository
) can be injected instead.
How does Field Injection cause runtime errors?
If dependencies are not correctly initialized by Spring, accessing them results in NullPointerException.
This problem occurs especially in unit tests, manual object creation, or misconfigured Spring Beans.
Example of a failing case:
If this class is instantiated manually (outside Spring context):
The userRepository
field is never initialized, causing NullPointerException.
How to fix it?
With Constructor Injection, the dependency must be provided:
Now, manual instantiation requires a valid dependency, avoiding runtime errors: