Constructor Injection Is The Recommended Approach To Dependency Injection
Some time ago, I applied for a role at a tech consultancy firm. I was required to complete a take-home assignment. The code would then be used during the interview to discuss with the interviewer about my approach. I was surprised when the interviewer mentioned constructor injection and said it was an anti-pattern and I should be using field injection instead. The recommended approach to Dependency Injection is constructor injection, and in this article, we shall see why.
In the following lousy example, SimpleController depends on a SimpleRepository class. We satisfy the dependency by instantiating inside the constructor. This is not a good practice because we have taken over the control. If SimpleRepository also depends on other classes, we must provide these dependencies.
class SimpleRepository {
}
class SimpleController {
private final SimpleRepository simpleRepository;
SimpleController() {
this.simpleRepository = new SimpleRepository();
}
}
With Spring, we get inversion of control as Spring will manage the instantiation of these dependencies for us.
@Repository
class SimpleRepository {
}
@Controller
class SimpleController {
private final SimpleRepository simpleRepository;
SimpleController(SimpleRepository simpleRepository) {
this.simpleRepository = simpleRepository;
}
}
Three ways to inject dependencies
1. Constructor Injection
The @Autowired is implicit in constructor injection which tells Spring to inject the SimpleRepository into SimpleController.
@Repository
class SimpleRepository {
}
@Controller
class SimpleController {
private final SimpleRepository simpleRepository;
SimpleController(SimpleRepository simpleRepository) {
this.simpleRepository = simpleRepository;
}
}
2. Setter Injection
We cannot declare the field as final when we use setter injection. The downside is that it can be modified later.
@Repository
class SimpleRepository {
}
@Controller
class SimpleController {
// private final SimpleRepository simpleRepository;
private SimpleRepository simpleRepository;
@Autowired
public void setSimpleRepository(SimpleRepository simpleRepository) {
this.simpleRepository = simpleRepository;
}
}
3. Field Injection
We are violating the basics of Java. The field is private, yet somehow we injected a dependency into the class. Spring uses reflection to achieve this. Therefore there is a chance that it may be null. We also cannot declare the field as final. Another downside is testability. We are not able to mock the dependency.
@Repository
class SimpleRepository {
}
@Controller
class SimpleController {
@Autowired
private SimpleRepository simpleRepository;
}
The argument for using field injection is that the code is cleaner as there is no need for constructor boilerplate code. If I have a class with ten dependencies, the constructor code becomes long and messy. A class with ten dependencies would likely mean that the architecture of your application is not well designed. Consider abstracting some business logic to other places.
The only time when field injection should be used is for writing tests.
@SpringBootTest
class SimpleControllerTest {
@MockBean
private SimpleRepository repository;
@Autowired
private SimpleController controller;
@Test
void contextLoads() {
Assertions.assertNotNull(repository);
Assertions.assertNotNull(controller);
}
}
Conclusion
The downsides of constructor injection are minimal compared to the other ways of dependency injection in Spring. Therefore, it is the recommended approach to dependency injection.