What is dependency injection?
Dependency Injection is a design pattern in object-oriented programming that allows a class to use the methods of another type without creating it directly but receiving it as an argument or field. This helps decouple classes and facilitates changing or reusing dependencies.
It is based on the principle of inversion of control, which proposes using abstractions (interfaces) instead of direct references between classes and making a class receive references to the components it needs to function instead of allowing it to instantiate them.
Dependency Injection can be implemented using an IoC (inversion of control) container, which is responsible for building and injecting services into clients according to the previous configuration.
It can also be done manually using different ways of injecting dependencies: in the constructor, in a method, in an instance variable, or in the method itself.
Key Concept: Inversion of Control
It is a software design principle in which the execution flow of a program is reversed compared to traditional programming methods, in which interaction is expressed imperatively by making calls to procedures or functions.
Inversion of control is based on delegating control of the flow to an external entity or architecture responsible for invoking application code according to desired responses to specific events or data requests.
Inversion of control is a generic term that can be implemented differently. For example, it can be implemented using events or dependency injection, the topic addressed in this article.
How to inject dependencies?
Dependency injection is a common practice in the Java programming environment (and also in other programming languages). This allows classes and code to be decoupled, thus facilitating the modification or reuse of dependencies.
Depending on where objects are supplied, there are different ways to perform dependency injection. Some of the most common forms are:
- Constructor injection: the dependency is passed as an argument to the constructor of the class that needs it.
- Method or setter injection: a method (which can be a setter or not) is created that receives the dependency as an argument and assigns it to a field of the class that needs it.
- Field or instance variable injection: a public field that receives the dependency directly from the DI container is declared.
- Method injection: the dependency is passed as an argument to the method used.
Pros... and cons?
Dependency injection has several advantages for software design and quality, including:
- Reduces coupling between classes, as it is based on abstractions (interfaces) rather than direct references.
- Facilitates changing or reusing dependencies, as different implementations of the same interface can be supplied.
- Improves code readability and cleanliness by eliminating lines dedicated to instantiating and configuring dependencies.
- Favors the development of unit tests, as simulated objects (mocks) or auxiliaries (stubs) can be injected to isolate the class's behavior under test.
- Allows delegating the creation and lifecycle of dependencies to a DI container, which is responsible for resolving them according to the previous configuration.
How do we do it in Java?
Many Java frameworks use dependency injection as one of their main features.
Some of the most popular ones are:
- If you're a Java developer, you've probably heard of Spring, an open-source framework offering a lightweight and flexible container for developing Java-based enterprise applications. Spring is based on dependency injection and offers features such as aspect-oriented programming, security, data access, integration, web, and microservices.
- Hibernate is another widely used tool in Java, which facilitates object-relational mapping (ORM) between Java classes and tables in a relational database. Hibernate uses dependency injection to configure and manage sessions, transactions, and database queries.
- JavaServer Faces (JSF) is an open-source framework that simplifies the development of web-based user interfaces using components. JSF uses dependency injection to access managed beans by the container and services other frameworks or libraries provide.
And among these well-known frameworks, we have Guice, a lightweight open-source framework that supports dependency injection using annotations to configure objects in Java.
It was developed by Google and released under the Apache 2.0 license.
Why Guice?
Google Guice and Spring Framework are two Java frameworks that provide support for dependency injection but differ in their approach, features, and scope.
Some of the most relevant differences are:
- Spring Framework is a much more comprehensive framework, offering many more functions than just dependency injection, such as security, data access, integration, web, and microservices. Google Guice is a lighter and more focused framework that focuses on dependency injection and leaves other functions to other frameworks or libraries, making it much more lightweight.
- Spring Framework supports two configuration styles: explicit and automatic. The exact configuration is done through XML files or annotated Java classes. Automatic configuration is done through annotations such as @Autowired or @ComponentScan, which allow the container to detect and inject dependencies automatically. Google Guice only supports explicit configuration through annotations such as @Inject or @Provides and classes that extend AbstractModule.
- Spring Framework allows both annotations and string identifiers to refer to dependencies. Google Guice only enables the use of annotations and avoids using string identifiers that can be error-prone or make refactoring difficult.
- Spring Framework uses reflection to create and inject objects, impacting performance and security. Google Guice uses code generated at compile time to create and inject objects, which can improve performance and security.
These crucial differences, mainly in scope and approach, make Google Guice an exciting candidate when choosing a framework for working with dependency injection in Java without needing the vast ecosystem of solutions offered by other larger and more comprehensive frameworks, such as Spring.