Field injection is evil

Why we use it?

Using Field injection is easy and provides low coding boilerplate. We want to inject Service1 bean into the class MyBean, just use @Autowired and it’s done.

@Component
 public class MyBean {
   @Autowired 
   private Service1 service1; 
}

The common problems:

1. Tight coupling:

One of the main advantages of using Spring is the ease of use of inversion of control(IOC) through dependency injection. This provides loose coupling between bean dependencies. Using Field injection beats the purpose of loose coupling as the @Autowired annotated attribute can only be injected through spring, making the bean tightly coupled with a spring DI container.

2. The God object problem:

You want to add dependencies to your class, just slap an @Autowired annotation to it and it’s done. However after some time we quickly realize that our class has grown in to a god class. This makes the class difficult to test and our class no longer follows the Single Responsibility Principle.

@Component
 public class MyBean { 
     @Autowired 
     private Service1 service1; 
     @Autowired 
     private Service2 service2;
     @Autowired 
     private Service3 service3;
     @Autowired 
     private Service4 service4;
     @Autowired 
     private Service5 service5;
     @Autowired 
     private Service6 service6;
     @Autowired 
     private Service7 service7;
     @Autowired 
     private Service8 service8;
 }
3. State safety

@Autowired annotated attributes cannot be final and thus are not sate safe. See the example below. We have a function myNewFunction() calling the launchCalculations() method of Service1. Now imagine a developer initializes the Service1 again by mistake. Service1 is no longer safe. MyBean class is not immutable.

@Component
public class MyBean {
  @Autowired 
  private Service1 service1; 

  public void myNewFunction() {
     service1 = new Service1();
     service1.launchCalculations();
  }
}
4. Testing

Say we have to test the MyBean class

@Component
 public class MyBean {
   @Autowired 
   private Service1 service1; 
}
  • We would have to use Mockito annotations to inject the dependencies.
  • What happens if someone adds another dependency BeanBus to Service1. Mockito will silently fail with a NullPointerException. We would have to add BeanBus with the @Mock annotation again.
  • What if we want real implementation of BeanCar dependency but not for BeanBus(Partial dependencies)? it will be more complicated to implement.
@RunWith(MockitoJUnitRunner.class)
 public class MyBeanTest {
   @Mock
   private BeanCar beanCAr; 
   @InjectMocks
   private Service1 service1; 
}
5. Dependency hiding.

Dependencies are not visible. We would need to open the bean implementation and check the dependencies.


The solution

Use constructor injection: Notice i did not use @Autowired on the constructor, it is because if there is only one constructor in a bean, Spring will know what to do with the dependency injection. However if you have more than one constructor, then you will have to add the @Autowired on the desired constructor.

@Component
 public class MyBean {
   private final Service1 service1; 
   public MyBean(final Service1 service1) {
     this.service1 = service1;
   }
}
  • Since final fields can be initialized in the constructor, our dependencies can be immutable, thus state safety is preserved.
  • Dependencies are no longer tightly coupled with the spring container dependency injection.
  • There is no dependency hiding, we will see all the required dependencies through the constructor.
  • If we are adding too many dependencies, we will quickly realize it as our constructor parameters will also increase.
  • While testing if we want to test the dependency Service1, we can just do:
BeanCar beanCar = new BeanCar();
BeanBus beanBus= Mockito.mock(BeanBus.class);
Service1 service1 = new Service1(beanCar,beanBus);

For partial dependency injection, we can do:

BeanCar beanCar = new BeanCar();
Service1 service1 = new Service1(beanCar,null);

Recommendation:

If you find writing constructor codes for a bean with all of its dependencies cumbersome, I recommend using project lombok. Lombok has the annotation @RequiredArgsConstructor.This create the constructor for you based on the final attributes. It will work so long there is no other constructor.

@RequiredArgsConstructor
@Component
 public class MyBean {
   private final Service1 service1; 
}

Leave a Reply