So far, we have talked about the code or framework that plays the role of dependency provider. It can be any custom code or full-fledged IoC container. Some developers refer to it as a DI container, but we will simply call it a container.
If we write custom code to supply dependency, things get smoother until we have just a single level of dependency. Take the scenario where our client classes are also dependent of some other modules. This results in chained or nested dependencies.
In this situation, implementing dependency injection will become quite complicated through manual code. That is where we need to rely on containers.
A container takes care of creating, configuring, and managing objects. You just need to do configuration, and the container will take care of object instantiation and dependency management with ease. You don't need to write any custom code such as that we wrote while implementing IoC with factory or service locator patterns.
So, as a developer, your life is cool. You just give a hint about your dependency, and the container will handle the rest and you can focus on implementing business logic.
If we choose containers to set dependencies for our Balance Sheet module, the container will create the objects of all dependencies first. Then, it will create an object of the Balance Sheet class and pass the dependencies in it. A container will do all these things silently and give you the object of the Balance Sheet module with all dependencies set in it. This process can be described with the following diagram:
In conclusion, the following are the advantages of using containers over manual code to manage dependency:
- Isolating the process of object creation from your code and making your code more clean and readable.
- Removing object wiring (setting dependency) code from your client module. The container will take care of object wiring.
- Making your modules 100 percent loose coupling.
- Managing the entire lifecycle of the modules. This is very helpful when you want to configure the objects for various scopes, such as request, session, and so on in application execution.
- Swapping out the dependency is just a matter of configuration—no change is required in the code.
- It is a more centralized way to handle object life span and dependency management. This is useful when you want to apply some common logic across the dependencies, for example, AOP in Spring. We will see details about AOP in Chapter 6, Aspect-Oriented Programming and Interceptors.
- Your module can benefit from advanced features that ship with containers.
Spring, Google Guice, and Dagger are some of the IoC containers available today for Java. Starting from Enterprise Edition version 6, Java introduced Context Dependency Injection (CDI), a dependency injection framework in Enterprise Edition. It's more or less similar to Spring's annotation-based DI implementation. Out of all the preceding containers, Spring is the most popular and widely used IoC container today.