Field injection is not recommended – Spring IOC
Introduction
When running a static code analysis tool or inspecting/analyzing your code from your IDE, you may have encountered the following warning regarding your @Autowired
fields:
Field injection is not recommended
This post shows the different types of injections available in Spring and what are the recommended patterns to use each of them.
Injection types
Although current documentation for spring framework (5.0.3) only defines two major types of injection, in reality there are three;
- Constructor-based dependency injection
- Setter-based dependency injection
- Field-based dependency injection
The latter is the one which the static code analysis tool complains about, but is regularly and extensively used.
You can even see this injection method on some of Spring guides although being discouraged in the documentation:
Constructor-based dependency injection
In constructor-based dependency injection, the class constructor is annotated with @Autowired
and includes a variable number of arguments with the objects to be injected.
1@Component
2public class ConstructorBasedInjection {
3
4 private final InjectedBean injectedBean;
5
6 @Autowired
7 public ConstructorBasedInjection(InjectedBean injectedBean) {
8 this.injectedBean = injectedBean;
9 }
10
11}
The main advantage of constructor-based injection is that you can declare your injected fields final, as they will be initiated during class instantiation. This is convenient for required dependencies.
Setter-based dependency injection
In setter-based dependency injection, setter methods are annotated with @Autowired
. Spring container will call these setter methods once the Bean is instantiated using a no-argument constructor or a no-argument static factory method in order to inject the Bean’s dependencies.
1@Component
2public class ConstructorBasedInjection {
3
4 private InjectedBean injectedBean;
5
6 @Autowired
7 public void setInjectedBean(InjectedBean injectedBean) {
8 this.injectedBean = injectedBean;
9 }
10
11}
Field-based dependency injection
In field-based dependency injection, fields/properties are annotated with @Autowired
. Spring container will set these fields once the class is instantiated.
1@Component
2public class ConstructorBasedInjection {
3
4 @Autowired
5 private InjectedBean injectedBean;
6
7}
As you can see, this is the cleanest way to inject dependencies as it avoids adding boilerplate code and there is no need to declare a constructor for the class. The code looks nice, neat and concise but as the code inspector already hinted us, there are some drawbacks to this approach.
Field-based dependency injection drawbacks
Disallows immutable field declaration
Field-based dependency injection won’t work on fields that are declared final/immutable as this fields must be instantiated at class instantiation. The only way to declare immutable dependencies is by using constructor-based dependency injection.
Eases single responsibility principle violation
As you know, in object-oriented computer programming, the SOLID acronym defines five design principles that will make your code understandable, flexible and maintainable.
The S in SOLID stands for single responsibility principle, meaning that a class should only be responsible for a single part of the functionality of the software application and all its services should be aligned narrowly with that responsibility.
With field-based dependency injection, it’s really easy to have lots of dependencies in your class and everything will look just fine. If constructor-based dependency injection is used instead, as more dependencies are added to your class, the constructor grows bigger and bigger and code starts to smell, sending clear signals that something is wrong.
Having a constructor with more than ten arguments is a clear sign that the class has too many collaborators and that maybe is a good time to start splitting the class into smaller and more maintainable pieces.
So although field-injection is not directly responsible for breaking the single responsibility principle it surely enough helps by hiding signals that otherwise would be really clear.
Tightly coupled with dependency injection container
The main reason to use field-based injection is to avoid the boilerplate code for getters and setters or creating constructors for your class. In the end, this means that the only way these fields can be set are by Spring container instantiating the class and injecting them using reflection, otherwise the fields will remain null and your class will be broken/useless.
The dependency injection design pattern separates the creation of class dependencies from the class itself transferring this responsibility to a class injector allowing the program design to be loosely coupled and to follow the Single responsibility and Dependency inversion principles (again SOLID). So in the end the decoupling achieved for the class by autowiring its fields is lost by getting coupled again with the class injector (in this case Spring) making the class useless outside of a Spring container.
This means that if you want to use your class outside the application container, for example for unit testing, you are forced to use a Spring container to instantiate your class as there is no other possible way (but reflection) to set the autowired fields.
Hidden dependencies
When using a dependency injection pattern, affected classes should clearly expose these dependencies using a public interface either by exposing the the required dependencies in the constructor or the optional ones using methods (setters). When using field-based dependency injection, the class is inherently hiding these dependencies to the outside world.
Conclusion
We’ve seen that field-based injection should be avoided whenever possible due to its many drawbacks however elegant it may seem. The recommended approach is then to use constructor-based and setter-based dependency injection. Constructor-based injection is recommended for required dependencies allowing them to be immutable and preventing them to be null. Setter-based injection is recommended for optional dependencies.
Comments in "Field injection is not recommended – Spring IOC"
However, I believe most of them are design problems, not a problem with the type of injection. Maybe the most sensible one is the immutability issue. If we ever had a chance to inject in final fields, it would be great.
Again, it's also not a big problem because almost every bean we inject are singletons and we don't initialize them more than once.
Cheers
1. "Disables immutable field declaration": this falsely assumes that developers would be prone to manually re-assing annotated injected fields. From personal experience (and just common sense), I can say this never happens. A team would have bigger issues if individual developers were making such a gross mistake.
2. "Eases SRP violation": so, a disadvantage of constructor DI (having to write extra code) is actually an advantage, because it would supposedly discourage developers from adding too many dependencies? Seriously...
3. Tightly coupled with DI container: this assumes that developers would actually write code that manually wires lots of "beans" and their dependencies together, rather than letting a tool/library do it for them. In practice, if a project uses a DI container, you expect to be able to use it or some other tool (personally, I use the JMockit library for that in JUnit tests), rather than writing potentialy thousands of lines of wiring code. Would be better then to avoid DI entirely.
4. "Hidden dependencies": this misses the fact that said dependencies normally are *internal* to the bean/component where they are injected into, and completely unknown to clients of the bean/component. That is, it makes no sense to expose such dependencies in the public API - as they actually *are* meant to be "hidden".
Thanks for your comments and insights.
Just as a reminder, this warning was introduced by the Spring Team themselves, and this post is just an explanation of what that may mean according to my opinions.
1. Teams are very diverse and skills may vary. I can tell you I've seen this happening on real projects. Unfortunately, good practices are not that extended across all companies. "A team would have bigger issues if individual developers were making such a gross mistake", exactly, so in order to prevent further issues, this may be a good start.
2. Yep, same as having classes with more than 200 lines is an automatic code-smell. If you have Sonar enabled with default rules, you'll also get an extra warning if your constructor exceeds 7 parameters (java:S107).
3. If you have something decoupled from the start, it might be easier to migrate or use the same library across different frameworks. With the rise of Quarkus, and Micronaut, I'm sure many would probably benefit from implementations that are agnostic of their application container. IMHO having to write tests that rely on DI is also a clear disadvantage and will probably affect performance.
4. The term expose here is used in terms of contract and not in terms of encapsulation. e.g. many IDEs warn you about missing bean declarations which are required for injections in another Bean. So if your component has constructor-based injection of certain interfaces, you know that somewhere in your application configuration you'll need to declare Beans implementing said dependencies.
I insist, these are just my opinions and, like everything on the Internet, should be taken with a grain of salt.
I use JMockit in some projects, I really appreciate you sharing your opinions.