Spring
May 19, 2020

M1 Q01 What is dependency injection and what are the advantages?

Dependency Injection is a technique of creating software in which objects do not create their dependencies on itself, instead, objects declare dependencies that they need and it is external object job or framework job to provide concrete dependencies to objects.

So, what does it actually mean? It's hard to understand it until we will have look at the code.

Life Without Dependency Injection

Firstly, let's look at the example without dependency injection. Here we have a simple runner class. This runner is using EployeesSalariesReportService.

The EployeesSalariesReportService has a simple goal that it needs to get all of the employees then calculate salary for each of the employees and then generate the PDF report.

Let's run it. As you can see it works correctly. It found all of the employees than it did calculate all of the salaries and did write the PDF report. So what's wrong with it?

Basically, the challenge is that if I wanted to modify the behaviour. For example, we don't want to write into PDF file but instead, I would like to write XlsSalaryReport I need to change the details of the implementation. And after I run it I will get xls report.

However, it would be great if you could modify the behaviour of this class without having to modify it. And what we could actually do to make it easier is that instead of creating this dependency over here. We could have this one inject that from external.

So do it we have to make SalaryReport interface that is im0plemented by PdfSalaryReport and also it implemented by XlsSalaryReport

So firstly, we create a local field and this local field we will get a reference to the SalaryReport interface. Then we will create a constructor for it. And instead of using PdfSalaryReport, we will call the SalaryReport.

Let's return to the Runner class and provide dependency new PdfSalaryReport(). And that will work fine and I can change it to XlsSalaryReport.

So this is the power of Dependency Injection that I am able to inject the dependencies without modifying the details of the implementation of the class.

Testing

Now the next challenge is the stability of this class. If I would like to test this class this is currently impossible because I am unable to inject any mocks into it since all of the classes are created over here under the method I would like to test.

Manual Dependency Injection

Let's go further to manual dependency injection. Here EmployeesSalariesReportService has a constructor that takes all of the external dependencies.

So according to the definition of dependency injection, instead of creating those dependencies on itself, this object expects that whose dependencies will be provided from external by someone external object.

And let's look at the Runner class which creates those objects and provide dependencies.

If we run this example it works exactly like the previous one but with the ability to change the behaviour of the class without having to change the details of the implementation.

Testing

The second thing that is also resolved with this approach is testability. If I would like to write the unit test for this one now I am able to. Because EmployeesSalariesReportService has a constructor that allows me to inject all of the dependencies via this constructor. And I can create all of the required mocks. I can record the behaviour and then I can verify mocks.

Annotation @InjectMocks will inject all mocks from external.

Now by adding a dependency injection tho this class we did not only make code easier to change and also more reuseable but this also we have other the possibility to achieve greater stability of the code.

Spring Dependency Injection

Now let's look at Spring dependency injection which is even better because in Spring we actually do not even create the EmployeesSalariesReportService on its own. We don't need to create all other dependencies. Instead, Spring will automatically scan for all over the beans that are available for us.

And this is done with @ComponentScan annotation. This annotation allows discovering all of the beans in our application

What Spring does for us is that it creates all of the dependencies and then it also sees that EmployeesSalariesReportService requires all of those dependencies and it will provide and inject all of the dependencies.

And the great thing about the Spring is also that you are able to set something that is called a profile. The profiles are a way to easily change the behaviour of the system without changing the code.

Let's look at the implementation of PdfSalaryReport and XslSalaryReport with profiles.

@Profile("pdf-reports") annotation means that PDF reports are available when we activate pdf-reports profile.

You can externalize this configuration. It is got even better because you can change the behaviour of the system without changing the code and having to recompile.

There are different types of dependency injection:

  • Constructor injection
  • Setter injection
  • Interface injection

The type that we discussed above in the example is the constructor injection.

The setter injection is basically a similar thing but instead of calling the constructor, we are supplying dependencies via setters.

The interface injection is something similar to the setter injection. But instead having setter we have an interface that requires us to implement a setter that gives us the ability to inject the dependency.

Advantages of using dependency injection are:

  • Increases code reusability
  • Increases code readability
  • Increases code maintainability
  • Increases code testability
  • Reduces coupling
  • Increases cohesion