1. Overview
In this article, we will discuss 3 different dependency injection strategy provided in Spring.
2. Content
Dependency Injection :
Dependency means one object requires another class to work with. Injection means a way to provide the required dependency to an object.
Example: HashSet creates an instance of HashMap. The HashSet will only use keys of HashMap to maintain a Set of unique elements.
The intent behind dependency injection is to have a separation of concern between constructor of objects and use of objects so we can have better readability and reusability of code.
The 3 different injection strategy are :
- Setter Injection
- Field Injection
- Constructor Injection (Best way to add dependency)
You can mix and match all the three approaches, but that is a kludgy/inelegant way of injecting dependencies.
At the end of this article I will explain a few parting thoughts on DI. There are reasonable explanations on why to use field or setter injection, but none of them are as good as compared to constructor injection.
3. Setter Injection
Setter injection is achieved by calling the setter method. The caller to setter method will be the IOC container. The call is made only after invoking no args constructor.
Let us take an example. Scenario is, once you get a response from outbound service that you made a call to, you do two things.
- Check for response status code and throw an exception if response code is not what we expected.
- If the response code is 2XX i.e. Successful family, then just reads the response and returns it.
ErrorHandler class would look like this :
public class ErrorHandler { public void handleError(Response response) { if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) { throw new IllegalStateException(); } // else do nothing. } }
ResponseHandler class would look like this :
@Named public class ResponseHandler { private ErrorHandler errorHandler; @Inject public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } public Response handleResponse(Response response) { errorHandler.handleError(response); ClientResponse clientResponse = response.readEntity(ClientResponse.class); return Response.ok().entity(clientResponse).build(); } }
3.1 Reasons you shouldn’t use setter injection?
- 3 lines of unnecessary boilerplate code per dependency added in class.
- Dependencies added in class are unnecessarily mutable.
- Class is also unnecessarily mutable because of the setter method.
4. Field Injection
In field injection we must declare a field that needs to be injected and annotate it with @Inject or @Autowired. This is the simplest of injection mechanisms.
@Named public class ResponseHandler { @Inject private ErrorHandler errorHandler; public Response handleResponse(Response response) { errorHandler.handleError(response); ClientResponse clientResponse = response.readEntity(ClientResponse.class); return Response.ok().entity(clientResponse).build(); } }
Quite frankly, Field injection is the worst injection mechanism. Using this immediately leads to usage of Mockito. You may write tests like this.
@RunWith(MockitoJUnitRunner.class) public class ResponseHandlerTest { @Mock private ErrorHandler errorHandler; @InjectMocks private ResponseHandler responseHandler; //Tests... }
Now if you inject any additional dependency in the ResponseHandler class, your test case will break. Because mockito expects the mock of newly added dependency. This is annoying.
4.1 Reasons you shouldn’t use field injection.
- The ultimate problem of field injection lurks in its simplicity. It looks cute enough to use it but can cause irreparable damage. The problem is, if there is any change and its dependency needs to be added you can add it easily. After few iterations you realize that the class has too many dependencies and does too many things. What you have now is a God class. Good luck refactoring it, if you ever intend to refactor.
- Dependencies added in class are unnecessarily mutable.
5. Constructor Injection
In constructor injection, we will provide the dependencies in the constructor.
@Named public class ResponseHandler { private final ErrorHandler errorHandler; @Inject public ResponseHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } public Response handleResponse(Response response) { errorHandler.handleError(response); ClientResponse clientResponse = response.readEntity(ClientResponse.class); return Response.ok().entity(clientResponse).build(); } }
This is the best way to inject dependencies.
5.1 Reasons you must use constructor injection
- We can mark all injected dependencies as private final.
- We can make all dependencies immutable.
- If the constructor accepts too many arguments, you know that this class is a high coupling class and hence needs to be refactored.
Constructor Injection is recommended by Spring Team. Read here.
6. Few words on field and constructor injection
Using any injection mechanisms won’t give you problems when you write code. It may sound obvious to add more dependencies when you write code. But once you finish writing it take a glance at the code you wrote. If it does not satisfy you then you need to refactor.
I think there can be few arguments in favor of setter and field injection. But I still don’t agree with them.
Argument 1: I have used Setter/Field injection for years and never had a problem. Why would I use constructor injection?
- First, constructor injection is not an obvious choice, or maybe we don’t even know that it exists. I for one didn’t know that there was constructor injection. Silly me.
- If you are using @Required annotation on setter(setter injection), why just not use Constructor injection. Because you cannot create instances if other dependencies are not created. No need for boiler plate code.
Argument 2: With setter injection, I can easily provide a mock to it.
- Why do you need a mock? Why do you need Mockito? Can’t you use a plain simple dependency injection in your test?
Argument 3: With setter/field injection I can inject several dependencies. If I do so with Constructor injection, then my code looks ugly.
- Excellent point. If you have more arguments in your constructor, then your class is highly coupled. You need to break it apart so the code doesn’t become god class.
7. Conclusion
That is all on the dependency injection mechanism in Spring. Favor constructor injection as against setter or field injection.