Why Constructor Injection is better in Spring?
In Spring, first it is important to understand the concept of Inversion Of Control (IoC). IoC is a software engineering principle which helps to transfer the control of objects to a Container. IoC is achieved through Dependency Injection in Spring.
Dependency injection is the process of providing the dependencies and IoC is the end result of Dependency Injection. We are also able to develop loosely coupled applications in Spring because of Dependency Injection.
In traditional programming, if we have to provide a dependency for an object on another object, we did it like below —
public class EmployeeService{
private Employee employee;
public EmployeeService(){
this.employee = new Employee();
}
}
Now, with Dependency Injection we can do the same without explicitly specifying the implementation of Employee class.
public class EmployeeService{
private Employee employee;
public EmployeeService(Employee employee){
this.employee = employee;
}
}
Spring IoC Container
An IoC container is common to frameworks that implement IoC. In Spring, the interface called ApplicatonContext is the IoC container. It is responsible for instantiating, configuring and assembling objects (also called beans), as well as managing their lifecycle.
@SpringBootApplication
public class EmployeePortal {
public static void main(final String[] args){
ApplicationContext applicationContext = SpringApplication.run(EmployeePortal.cass,args);
}
}
In Spring, Dependency Injection can be done via Constructor Injection, Setter Injection and Field Injection. Let’s discuss about all of them and which is the better approach and why.
Constructor-Based Injection
In this case, the container will invoke a constructor with arguments each representing a dependency we want to set.
@Component
public class EmployeeService{
private Employee employee;
public EmployeeService(Employee employee){
this.employee = employee;
}
}
Before Spring 4.3, we had to add an @Autowired
annotation to the constructor. With newer versions, this is optional if the class has only one constructor.
@Component
public class EmployeeService{
private Employee employee;
private DataRefresh dataRefresh;
public EmployeeService(Employee employee){
this.employee = employee;
}
@Autowired
public EmployeeService(Employee employee, DataRefresh dataRefresh){
this.employee = employee;
this.dataRefresh = dataRefresh;
}
}
When we have a class with multiple constructors, we need to explicitly add the @Autowired
annotation to any one of the constructors so that Spring knows which constructor to use to inject the dependencies.
Setter-Based Injection
In setter-based injection, we create a setter method and provide the required dependencies as field parameter.
@Component
public class EmployeeService{
private Employee employee;
@Autowired
void setEmployee(Employee employee){
this.employee = employee;
}
Employee getEmployee(){
return this.employee;
}
}
EmployeeService class requires a dependency of Employee class. Employee object is provided as an argument of the setter method defined. Spring will find the @Autowired
annotation and call the setter to inject the dependency.
Field-Based Injection
With field-based injection, Spring assigns the required dependencies directly to the fields on annotating with @Autowired
annotation.
@Component
public class EmployeeService{
@Autowired
private Employee employee;
void setEmployee(Employee employee){
this.employee = employee;
}
Employee getEmployee(){
return this.employee;
}
}
If we add @Autowired
in both field and setter, IoC will use setter-based injection because field-based injection uses reflection, hence it’s more costlier.
Which approach is better?
Constructor-based injection has more advantages than other two approaches for the following reasons :
- The dependencies are clearly identified. We can be 100% sure that a class will never be instantiated if any of it’s dependencies are not injected. This also helps in preventing null pointer exception.
- Dependencies can be final which promotes robustness and thread-safety.
- Identifying code smells, helps us knowing if our bean is dependent on too many objects. If the constructor has too many arguments, it means that the class has too many responsibilities which violates the Single Responsibility Principle.
- Constructor injection simplifies writing unit tests. The constructor forces us to provide valid objects for all dependencies. Using mocking libraries like Mockito, we can create mock objects that we can then pass into the constructor.
- Promote Immutability. If we want our bean to be immutable, we can initialize it once and for all during the constructor invocation and not alter its dependencies at a later point of time.
Reference : www.baeldung.com, reflectoring.io/