10k

Spring cookbook - Field injection? Constructor injection? Or setter injection?

Field injection? Constructor injection? Or setter injection?

  1. What are they

    Different flavors of dependency injection

    ```java class MyComponent {

    @Inject MyCollaborator collaborator;

    public void myBusinessMethod() { collaborator.doSomething(); } } ```

    DI is the means to connect the two.

  2. Why field injection is evil

    Field injection allows dependencies to be null by default

    Here is another way of injection:

    ```java class MyComponent {

    private final MyCollaborator collaborator;

    @Inject public MyComponent(MyCollaborator collaborator) { Assert.notNull(collaborator, "MyCollaborator must not be null!"); this.collaborator = collaborator; }

    public void myBusinessMethod() { collaborator.doSomething(); } } ```

    1. You can only create instances of MyComponents by providing a MyCollaborator. -> enforcing object has a valid state after construction
    2. You communicate mandatory dependencies publicly.(expose the dependencies on the interface)
    3. Final fields ensure that dependencies cannot be changed after object creation, contributing to the overall safety and predictability of the code.

    If you have too many dependencies, field injection can reduce the pain of setter or constructor injection, but, too many dependencies may remind you that you should split the component.

  3. Testability

    Consider the following class using constructor injection:

    ```java public class MyComponent { private final MyCollaborator collaborator;

    public MyComponent(MyCollaborator collaborator) { this.collaborator = collaborator; }

    public void myBusinessMethod() { collaborator.doSomething(); } } ```

    Now, let's write a unit test for the myBusinessMethod() using constructor injection:

    ```java import static org.mockito.Mockito.*;

    public class MyComponentTest {

    @Test public void testMyBusinessMethod() { // Create a mock collaborator MyCollaborator collaboratorMock = mock(MyCollaborator.class);

     // Create an instance of MyComponent with the mock collaborator
     MyComponent component = new MyComponent(collaboratorMock);
    
     // Call the method under test
     component.myBusinessMethod();
    
     // Verify that collaborator's doSomething() method was called
     verify(collaboratorMock).doSomething();
    

    } } ```

    In this example, we use Mockito to create a mock collaborator object. We then instantiate MyComponent with this mock collaborator injected via the constructor. This allows us to isolate the component under test and verify its behavior by asserting that the collaborator's doSomething() method was called.

    Now, let's consider how we would test the same functionality with field injection:

    ```java public class MyComponent { @Inject private MyCollaborator collaborator;

    public void myBusinessMethod() { collaborator.doSomething(); } } ```

    And the corresponding test using field injection:

    ```java import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations;

    import static org.mockito.Mockito.*;

    public class MyComponentTest {

    @Mock private MyCollaborator collaboratorMock;

    @InjectMocks private MyComponent component;

    @Before public void setup() { // Initialize mocks MockitoAnnotations.initMocks(this); }

    @Test public void testMyBusinessMethod() { // Call the method under test component.myBusinessMethod();

     // Verify that collaborator's doSomething() method was called
     verify(collaboratorMock).doSomething();
    

    } } ```

    In this example, we use Mockito annotations (@Mock and @InjectMocks) to inject the mock collaborator into the component under test. However, this approach relies on reflection and can be more error-prone and less readable than constructor injection, as it obscures the dependencies being injected.

    Overall, constructor injection provides a more straightforward and reliable approach to testing by explicitly passing dependencies to the class constructor, resulting in cleaner and more maintainable unit tests.

  4. Lombok

    Helps you finish the "tedious" part of contractor injection

  5. Additional - constructor injection vs setter injection

    We usually advise people to use constructor injection for all mandatory collaborators and setter injection for all other properties.


    Setter injection + @Required can be used as constructor injection.

    Constructor injection should be used for mandatory dependencies (which are those who are required for the functionality of the object )and setter injection for optional dependencies

    Constructor injection must pass the dependencies at first while setter injects can be at the time they are used.

  6. Reference : https://odrotbohm.de/2013/11/why-field-injection-is-evil/

Thoughts? Leave a comment