Field injection? Constructor injection? Or setter injection?
-
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.
-
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(); } } ```
- You can only create instances of MyComponents by providing a MyCollaborator. -> enforcing object has a valid state after construction
- You communicate mandatory dependencies publicly.(expose the dependencies on the interface)
- 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.
-
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'sdoSomething()
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.
-
Lombok
Helps you finish the "tedious" part of contractor injection
-
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.
-
Reference : https://odrotbohm.de/2013/11/why-field-injection-is-evil/