Dependency injection across fields is bad practice

Translated by Field Dependency Injection Considered Harmful by Vojtech Ruzicka

image

Deploying dependencies across fields is a very popular practice in DI frameworks such as Spring. However, this method has some serious tradeoffs and should therefore be avoided more often.

Types of Implementations


There are three main ways to inject your dependencies into a class: through the constructor, setter, and field. Let's quickly compare the code with the same dependencies implemented with each of the approaches.

Constructor


private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;
@AutowiredpublicDI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC){
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}

Setter


private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;
@AutowiredpublicvoidsetDependencyA(DependencyA dependencyA){
    this.dependencyA = dependencyA;
}
@AutowiredpublicvoidsetDependencyB(DependencyB dependencyB){
    this.dependencyB = dependencyB;
}
@AutowiredpublicvoidsetDependencyC(DependencyC dependencyC){
    this.dependencyC = dependencyC;
}

Field


@Autowiredprivate DependencyA dependencyA;
@Autowiredprivate DependencyB dependencyB;
@Autowiredprivate DependencyC dependencyC;

What's wrong?


As you can see, the implementation option through the field looks very attractive. It is very concise, expressive, there is no template code. The code is easy to navigate and read. Your class can simply focus on the core functionality and is not cluttered with boilerplate DI code. You simply put the @Autowired annotation above the field - and that’s it. No need to write special constructors or setters just to ensure that the DI container provides the necessary dependencies. Java is pretty verbose in itself, so it’s worth using every opportunity to make the code shorter, right?

Violation of the principle of sole responsibility


Adding new dependencies is easy. Maybe even too simple. There is no problem adding six, ten or even more dependencies. When using constructors for implementation, after a certain point, the number of constructor arguments becomes too large and it immediately becomes obvious that something is wrong. Having too many dependencies usually means that a class has too many areas of responsibility. This may be a violation of the principles of single responsibility and separation of responsibilities ( original: separation of concerns) and is a good indicator that the class may be worth more carefully studying and refactoring. When using implementation through fields, there is no such explicit alarm indicator, and thus there is an unlimited growth of embedded dependencies.

Dependency Hiding


Using a DI container means that the class is no longer responsible for managing its dependencies. The responsibility for receiving them is transferred from the class to the outside and now someone else is responsible for providing them: it can be a DI container or manual provision of them through tests. When a class is no longer responsible for obtaining dependencies, it must explicitly interact with them using public interfaces — methods or constructors. Thus, it becomes clear what the class requires, as well as whether it is optional dependencies (via setters) or mandatory (constructor)

DI container dependency


One of the key ideas of DI frameworks is that the managed class should not depend on the specific container used. In other words, it should be a simple POJO class, an instance of which can be created independently if you pass all the necessary dependencies to it. Thus, you can create it in a unit test without launching the container and test it separately (with a container it will be more an integration test). If you don’t have a container tie, you can use the class as managed or unmanaged, or even switch to another DI framework.

However, when you inject directly into the fields, you do not provide a direct way to create an instance of the class with all the necessary dependencies. It means that:

  • There is a way (by calling the default constructor) to create an object using new in a state where it lacks some of its required dependencies, and using it will result in a NullPointerException
  • Such a class cannot be used outside of DI containers (tests, other modules) and there is no way, besides reflection, to provide it with the necessary dependencies

Invariance


Unlike the method using the constructor, implementation through fields cannot be used to assign dependencies to final fields, which leads to your objects becoming mutable

Implementation via constructor vs setter


Thus, injection through the fields may not be a good way. What is left? Setters and constructors. Which one should I use?

Setters


Setters should be used to inject optional dependencies. A class must be able to function, even if they were not provided. Dependencies can be changed at any time after the creation of the object. This may or may not be an advantage depending on the circumstances. It is sometimes preferable to have an immutable object. Sometimes it’s useful to change the constituent parts of an object at runtime - for example, managed MBeans in JMX.
The official Spring 3.x documentation recommendation encourages the use of setters over constructors:
The Spring team mainly advocates for injection through setters, because a large number of constructor arguments can become cumbersome, especially if properties are optional. Setters also make objects of this class suitable for reconfiguration or re-injection later. Management through JMX MBeans is a prime example.

Some purists prefer constructor-based injection. Providing all the dependencies means that the object always returns to the calling code in a fully initialized state. The disadvantage is that the object becomes less reconfigurable and re-injectable

Constructors


Injection through constructors is good for binding dependencies - those that are required for the correct functionality of an object. Passing them through the constructor, you can be sure that the object is completely ready for use from the moment of creation. The fields assigned in the constructor can also be final, which allows the object to be completely unchanged or at least protects the necessary fields.

One of the consequences of using embedding through the constructor is that it is now impossible to cycle between two objects created in this way (as opposed to embedding through the setter). This is more a plus than a limitation, since cyclic dependencies should be avoided, which is usually a sign of poor architecture. Thus, a similar practice is prevented.

Another advantage is that when using Spring versions 4.3+, you can completely decouple your class from a specific DI framework. The reason is that Spring now supports implicit constructor injection for single-use case scenarios. This means that you no longer need DI annotations in your class. Of course, you can achieve the same result by explicitly configuring DI in the Spring settings for this class; it’s just easier to do now.

As for Spring 4.x, the official recommendation from the documentation has changed and now injection through the setter is no longer preferred over the constructor:
The Spring team mainly advocates injection through the constructor, as it allows you to implement application components as immutable objects and to ensure that the required dependencies are not null . Moreover, components implemented through the constructor always return to the client code in a fully initialized state. As a small remark, a large number of constructor arguments is a sign of "code with a zip" and implies that the class probably has too many responsibilities and needs to be reorganized to better resolve the issue of separation of responsibilities.

Setter injection should be used primarily for optional dependencies that can be assigned default values ​​within the class. Otherwise, check fornot-null should be used wherever code uses these dependencies. One of the advantages of using embedding through setters is that they make class objects reconfigurable and re-injectable later

Conclusion


Basically, you should avoid embedding through fields. Alternatively, setters or constructors should be used for implementation. Each of them has its own advantages and disadvantages depending on the situation. However, since these approaches can be mixed, this is not an “either-or” choice, and you can combine injection in the same class both through the setter and through the constructor. Constructors are more suitable for mandatory dependencies and for the need for immutable objects. Setters are better suited for optional dependencies.

Also popular now: