Spring Framework is renowned for its robust Dependency Injection (DI) capabilities, which simplifies the management of dependencies and its lifecycle of beans in a Java application.
This article delves into how to use @Autowired
for field, constructor, and setter injection methods in Spring,
and how these methods compare to traditional Java dependency management.
What is Dependency Injection?
Dependency injection is a design pattern that allows an object to receive its dependencies from an external source rather than creating them internally. This pattern promotes loose coupling, making code more modular, testable, and easier to manage.
Example to illustrate DI and reduced coupling
// Concrete implementation of InventoryService
public class InventoryServiceImpl implements InventoryService {
@Override
public void checkStock(Order order) {
// some implementation
}
}
Without DI:
public class OrderService {
// OrderService depends on concrete implementation directly
private InventoryServiceImpl inventoryService = new InventoryServiceImpl();
public void placeOrder(Order order) {
inventoryService.checkStock(order);
// other logic
}
}
With DI:
public class OrderService {
private InventoryService inventoryService;
// OrderSrvice depends on the interface rather than the concrete class
public OrderService(InventoryService inventoryService) {
// Constructor injection
this.inventoryService = inventoryService;
}
public void placeOrder(Order order) {
inventoryService.checkStock(order);
// other logic
}
}
In the first example, OrderService
is tightly coupled with the concrete implementation of InventoryService
.
In the second example, the dependency is injected, allowing OrderService
to be independent of the concrete implementation
of InventoryService
. This reduces coupling and enhances flexibility, making it easier to change InventoryService
implementation or mock it in tests.
Spring’s @Autowired
annotation
The @Autowired
annotation in Spring is used to inject dependencies automatically. This can be done via
field injection, constructor injection, and setter injection.
1. Field injection
Field injection involves directly injecting dependencies into a field of a class. This is the simplest form of dependency injection in Spring.
Example:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyService {
@Autowired
private MyRepository myRepository;
public void performService() {
// calls myRepository
}
}
The @Autowired
annotation on myRepository
tells Spring to inject an instance of MyRepository
into this field.
Spring will then automatically creates an instance of MyRepository
(or retrieves an existing one if it is a singleton)
and injects it into MyService
when MyService
is created. This is done through Spring’s Inversion of Control (IoC) container
which automatically manages the lifecycle and dependencies of beans.
In this case, MyService
is able to use MyRepository
without explicitly instantiating it, promoting loose coupling and
adherance to the dependency inversion principle.
2. Constructor injection
In many cases, constructor injection might be preferred as it ensures that dependencies are provided at object creation time and enforces immutability.
Example:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyService {
private final MyRepository myRepository;
@Autowired
public MySerivce(Repository repository) {
myRepository = repository;
}
public void performService() {
// calls myRepository
}
}
In this case, @Autowired
on the constructor tells Spring to use this constructor for dependency injection.
When Spring creates the MyService
bean, it recognizes that MyService
has a constructor with a single parameter of type MyRepository
.
Spring will then create an instance of MyRepository
and passes it as an argument to the MyService
constructor.
Example with multiple constructors:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyService {
private final MyRepository myRepository;
private final AnotherDependency anotherDependency;
// Primary constructor for dependency injection
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
this.anotherDependency = null; // Or handle appropriately
}
// Another constructor
public MyService(AnotherDependency anotherDependency) {
this.myRepository = null; // Or handle appropriately
this.anotherDependency = anotherDependency;
}
public void performService() {
// Use myRepository or anotherDependency
}
}
When a class has multiple constructors, specifying @Autowired
is crucial because it tells Spring explicitly which
constructor to use for dependency injection (i.e. ensures that Spring knows which dependencies to inject when the class
is instantiated).
3. Setter injection
Similar to the previous example, setter injection involves providing dependencies via setter methods. This approach is useful when the dependencies are optional or when you want to change them after object’s creation.
Example:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyService {
private MyRepository myRepository;
@Autowired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
public void performService() {
// Use myRepository
}
}
Comparison with traditional Java code
In traditional java code, dependencies are typically managed manually:
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myrepository = myRepository;
}
public void performService() {
// Use myRepository
}
}
public class Main {
public static void main(String[] args) {
// Manual wiring of dependencies
MyRepository myRepository = new MyRepository();
MyService myService = new MyService(myRepository);
myService.performService();
}
}
There are certain issues regarding this:
1. Tight coupling
In this case, the MyService
class is tightly coupled with MyRepository
. In traditional approach, classes instantiate their
dependencies directly. Any change in MyRepository
(e.g. constructor change, needing additional parameters) would require a change in
MyService
.
2. Testing difficulties
There is no easy substitution of MyRespository
as MyService
creates its own MyRepository
instance.
However, personally I think these issues are more of a poor design problem rather than whether the codebase is using traditional or Spring.
3. Poor scalability
While the first two issues often stem from poor design practices, regardless of whether a project uses traditional java, it is difficult to manage all these manual wiring of dependencies as our application grow.
public class MyServiceTest {
@Test
public void testPerformService() {
MyService myService = new MyService(); // Uses real MyRepository
myService.performService();
// Hard to test because MyRepository is not mockable
}
}
Benefits of Spring Boot @Autowired
With Spring Boot, we don’t need to manually configure dependencies.
The framework automatically scans for components (@Autowired
, @Component
, @Service
, @Repository
, etc.) and wires them appropriately.