In this article, I will answer a question I’m often asked by people either on YT, Twitter, LinkedIn or different presentations I deliver. Dependency injection is probably the most used capability an application framework (such as Spring) offers.
We generally use @Autowired in Spring to get a reference to a bean in Spring context. I say generally, because on rare occasions you’d also see apps using @Inject (that annotation borrowed from CDI). But @Inject is pretty rare, so let’s consider @Autowired for our current example.
The @Autowired annotation can be used in three ways:
Let’s take them one by one and discuss their advantages and disadvantages.
The next code snippet shows you an example of field dependency injection:
1 2 3 4 5 6 7 8 | @Component public class LoginService { @Autowired private UserRepository userRepository; // Omitted code } |
In the next table you find enumerated the advantages and disadvantages for using field injection.
Advantages | Disadvantages |
– The shortest way to inject a dependency. You only need to add the @Autowired over the field, and that’s it. | – Can’t be used with immutable fields. It forces you define the attributes mutable. – More difficult to set a mock dependency in a unit test. |
For simplicity, you’ll often find field dependency injection is used in examples and tutorials. In a real-world app, however, you’d prefer to make sure once Spring injects a value in a specific field no one else can change it. I saw projects with code re-instantiating the fields. Suddenly you ended up with a situation where a specific Spring capability wasn’t working anymore and after several hours of investigation you find out that the reason is the reference wasn’t of a bean in the Spring context.
Trust me, for the sake of the app, you want injected fields to stay immutable. And, in Java, you can make sure a value of a field isn’t changed after initialization if you make it immutable. With field dependency injection you can’t, however, make the field immutable, because, with field dependency injection you need to allow Spring to set a value after the instance is created.
The second disadvantage appears when you want to write a unit test without using a mocking library. A mocking library such as Mockito gives us many possibilities. My favorite is using simple annotations such as @Mock and @InjectMocks to work with mock objects in unit tests. These annotations work fine for all the dependency injection approaches (including field dependency injection). But, in case you can’t use Mockito or a library that offers similar capabilities in your app, then setting the dependency is more difficult. If you have a constructor to provide the dependencies when you create the object instance you test, this problem is solved.
The next code snippet shows you an example of constructor dependency injection.
In this code snippet, I added @Autowired annotation so you can easily spot where the dependency injection happens. However, be aware that this annotation is optional if you have only one constructor (like in this case), and you don’t have to explicitly use it with the constructor. Even without it, Spring injects the values in the constructor’s parameters when it creates the LoginService bean.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Component public class LoginService { private final UserRepository userRepository; @Autowired public LoginService(UserRepository userRepository) { this.userRepository = userRepository; } // Omitted code } |
Looking at the code snippet, it might initially seem a little bit more complicated than the field injection. And, leaving it like this, it is. But today things are quite simple with dependencies such as Lombok which allows you create the constructor and getters and setters with only a few annotations over the class.
Using constructor injection, you gain the chance of adding instructions before the instance creation ends, but I don’t recommend you doing this. Logic added to the constructor adds complexity and might also lead to performance problems depending on what kind of logic you add to the constructor. My recommendation is to keep the constructor plain, as you see it in the previous snippet.
Then, what’s the real advantage of constructor injection?
Let’s discuss these points. The first point might be easier to observe and understand. in Java, you cannot define a final field, unless you give it an initial value. Usually, with non-static fields, you give them values through the constructor, as it offers you the possibility of having different values for that attribute for different object instances.
Having final fields, you make sure that once Spring assigned a value to these attributes, the values cannot be overridden by your app. Of course, you may say that the developer doesn’t have to implement code that assigns new values to these attributes, but trust me, it happens if you don’t make sure somehow it’s not possible to do this. And once you have an app where field values are changed repeatedly, the app’s maintenance becomes a nightmare. Better avoid this scenario.
The second point is about using the constructor to set the dependencies in tests. Take a look at the next code snippet. It shows you a sample of an implementation of a unit test where we don’t use any mocking library.
1 2 3 4 5 6 7 8 9 10 11 | public class TestLoginService { @Test @DisplayName("Sample unit test with JUnit for LoginService") public void sampleTest() { UserRepository repository = new SomeMockUserRepositoryImplementation(); LoginService service = new LoginService(repository) // call some method from LoginService and assert the results. } } |
See the idea? You don’t need to have a mocking library to test your app. Sometimes, using Mockito in old/legacy apps isn’t that easy. But if you inject the dependencies through the constructor, you don’t have to expose them publicly, neither you have to implement setters (which is anyway not a great idea), but you can still implement tests for the logic. This is great!
One might argue that the second point isn’t that valuable today, as we indeed use mocking libraries mostly, but it’s still an advantage to consider. However, the fact that you have your beans immutable (point 1) is invaluable.
Advantages | Disadvantages |
– You can define immutable instances by making the attributes final. – You can define dependencies in unit tests directly when you create the instance of bean you want to test. | – You need to write the constructor (which is more code in the class than in the field injection). However, with a dependency such as Lombok, this disadvantage disappears as you’ll learn later in this article. |
The next code snippet shows you an example of setter dependency injection.
1 2 3 4 5 6 7 8 9 10 11 12 | @Component public class LoginService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } // Omitted code } |
As you observe, the setter injection simply means Spring calls the setter of an attribute to set a value to a specific attribute. Spring injects the value in the setter’s parameter which is the (usually) assigned to the attribute. Sometimes developers argue that you could add validations in the setter and this is an advantage of the setter injection. However, I do not agree and never recommend such an approach. This approach, which is also known as “smart setters” is discussed in Clean Code by Robert C. Martin. I will let you read the book and find out more from Uncle Bob’s examples why is this not a good approach.
Basically, setter injection brings no advantage but has disadvantages. You cannot implement immutability and you have to write more code to define each setter. This is the least approach I would recommend you use.