Constructors & DI

I’m a big advocate of constructor injection, but a recent office debate caused me to take a step back and reevaluate my beliefs.

I thought I’d take a stab at some good and bad points of different ways of injecting dependencies into a class using Spring.

Field Injection

@Component
public class FieldInjectionExample {

    @Value("${url.value}")
    private String url;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    public String doSomething() throws IOException {
        final String object = restTemplate.getForObject(url, String.class);
        return String.valueOf(objectMapper.readValue(object, String.class));
    }
}

So here you can see that my class looks nice and clean, there are very few lines of code require to construct my object. Although the fields aren’t final, there are no setters so you could only modify them using Reflection. It’s fairly succinct, and you could see all the things needed to construct a class. So, I think I probably need write some tests for this class.

public class FieldInjectionExampleTest {
    
    private FieldInjectionExample fieldInjectionExample;
    
    @Before
    public void setup(){
        fieldInjectionExample = new FieldInjectionExample();
    }
    
    @Test
    public void testDoSomething() throws IOException {
        assertThat(fieldInjectionExample.doSomething()).isEqualTo("?");
    }
}

So I think the first thing you can spot, is that I am able to create my object under test without necessarily being aware of the dependencies required to make the class do it’s job, regardless of whether or not they are being mocked.

What constructor injection would give us here is an explicit contract, which documents exactly what is needed to do it’s job -it will fail at start up time, rather than at run time.

This will allow you to also determine if you need to create any beans as configuration (maybe a non-spring managed component), as you can check the constructor.

Take the assumption that I have now run my test, and it has failed. I’ve realised (or remembered) that my class needs a few things to make it work.

@RunWith(MockitoJUnitRunner.class)
public class FieldInjectionExampleTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private ObjectMapper objectMapper;

    @InjectMocks
    private FieldInjectionExample fieldInjectionExample;

    @Test
    public void testDoSomething() throws IOException {
        final String returnedObject = "String";
        when(restTemplate.getForObject(anyString(), any(Class.class))).thenReturn(returnedObject);
        when(objectMapper.readValue(anyString(), any(Class.class))).thenReturn(returnedObject);
        final String something = fieldInjectionExample.doSomething();
        assertThat(something).isEqualTo("String");
    }

}

Ok, so here I’m pretty sure that I’ve captured everything that I need, but I will never be sure until it fails.

Setter Injection

It’s entirely possible to place @Autowired annotations on setter methods of objects. This can be a neat way of instantiate a class without having all the dependencies created at run time, but I believe that this comes with down sides, such as how easy it becomes to create circular dependencies, and spot them until it’s too late.

@Component
public class SetterInjectionExample {
    
    private String url;
    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;
    
    public String doSomething() throws IOException {
        final String object = restTemplate.getForObject(url, String.class);
        return String.valueOf(objectMapper.readValue(object, String.class));
    }

    @Autowired
    public void setUrl(@Value("${url.value}") final String url) {
        this.url = url;
    }

    @Autowired
    public void setRestTemplate(final RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Autowired
    public void setObjectMapper(final ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
}

This comes with much more code, and the ability to change the object at run time.

public class SetterInjectionExampleTest {

    private SetterInjectionExample setterInjectionExample;

    @Before
    public void setup(){
        setterInjectionExample = new SetterInjectionExample();
    }

    @Test
    public void testDoSomething() throws IOException {
        assertThat(setterInjectionExample.doSomething()).isEqualTo("?");
    }
}

This still has the same problem – you cannot easily find what is required to create and use the class. There is an expectation that this would work.
You can inject your mocks the same way you would as if you were injecting field variables.

@RunWith(MockitoJUnitRunner.class)
public class SetterInjectionExampleTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private ObjectMapper objectMapper;

    @InjectMocks
    private SetterInjectionExample setterInjectionExample;

    @Test
    public void testDoSomething() throws IOException {
        final String returnedObject = "String";
        when(restTemplate.getForObject(anyString(), any(Class.class))).thenReturn(returnedObject);
        when(objectMapper.readValue(anyString(), any(Class.class))).thenReturn(returnedObject);
        final String something = setterInjectionExample.doSomething();
        assertThat(something).isEqualTo("String");
    }
}

This still means that, although you have setters available, it still isn’t clear what is needed in order for the class to do its job. Maybe it could be useful for optional parameters.

Constructor Injection

Here is the same class, with dependencies injected into the constructor.

@Component
public class ConstructorInjectionExample {
    private final String url;
    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;

    @Autowired
    public ConstructorInjectionExample(@Value("${url.value}") final String url,
                                       final RestTemplate restTemplate,
                                       final ObjectMapper objectMapper) {
        this.url = url;
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }
    
    public String doSomething() throws IOException {
        final String object = restTemplate.getForObject(url, String.class);
        return String.valueOf(objectMapper.readValue(object, String.class));
    }

}

It’s really clear to see what your class needs in order to do it’s job. I cannot instantiate this class without having a URL, a RestTemplate and an ObjectMapper.

This doesn’t mean that you cannot inject your mocks if you so wish, but it does allow to see what the class needs to do it’s job.

@RunWith(MockitoJUnitRunner.class)
public class ConstructorInjectionExampleTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private ObjectMapper objectMapper;

    @InjectMocks
    private ConstructorInjectionExample constructorInjectionExample;

    @Test
    public void testDoSomething() throws IOException {
        final String returnedObject = "String";
        when(restTemplate.getForObject(anyString(), any(Class.class))).thenReturn(returnedObject);
        when(objectMapper.readValue(anyString(), any(Class.class))).thenReturn(returnedObject);
        final String something = constructorInjectionExample.doSomething();
        assertThat(something).isEqualTo("String");
    }
}

In order to understand the different ways of injection properties, I’ve broken down the key points in a table. This will allow you to make an informed decision as to which one is appropriate for your work.

Field Setter Ctor
Cannot instantiate without necessary
properties
X
Cannot create circular dependencies X X
Clearly defined requirements in one place X
Potential for easy readability X X
Allow the use of the ‘final’ keyword X
Obvious when breaking SRP X
Useful for non-mandatory properties X

What Does Spring Say?

Finally, what does Spring say about the most appropriate form of property injection?

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.