Dependency injection (DI). This article describes the concept of dependency injection from a Java perspective.
1. Dependency injection in Java
1.1. What is dependency injection?
Dependency injection (DI) is a concept in which objects receive their required dependencies from external sources rather than creating them internally. DI can be implemented in any programming language. The general concept behind dependency injection is called Inversion of Control (IoC).
A Java class has a dependency on another class if it uses an instance of that class. This is known as a class dependency. For example, a class that accesses a logging service has a dependency on the service class.
Ideally, Java classes should be as independent as possible from other Java classes. This improves their reusability and allows them to be tested independently of other classes.
If a Java class creates an instance of another class using the new
operator, it cannot be easily tested in isolation, as it has a hard dependency.
The following example demonstrates a class without hard dependencies:
package com.vogella.tasks.ui.parts;
import java.util.logging.Logger;
public class MyClass {
private Logger logger;
public MyClass(Logger logger) {
this.logger = logger;
// Write an info log message
logger.info("This is a log message.");
}
}
This is a standard Java class. Its only distinguishing characteristic is that it avoids directly creating objects. |
A framework class, often referred to as the dependency container, can analyze the dependencies of a class. Using this analysis, it can create an instance of the class and inject the required objects into the class’s defined dependencies using Java reflection.
By avoiding hard dependencies, a Java class becomes decoupled from specific implementations, making it easier to test. For example, you can test such a class in isolation using mock objects.
Mock objects (mocks) are substitutes that mimic the behavior of real objects. Unlike real objects, mocks are not programmed but are configured to behave in specific, predefined ways. The term "mock" means to mimic or imitate.
When dependency injection is used, a Java class can be tested in isolation, enabling better test coverage and maintainability.
1.2. Using annotations to describe class dependencies
There are several ways to describe the dependencies of a class. One of the most common approaches is using Java annotations to define dependencies directly within the class.
The standard Java annotations for defining dependencies were introduced in Java Specification Request 330 (JSR 330).
This specification defines the @Inject
and @Named
annotations, which are now maintained and provided by the Jakarta project.
The following example demonstrates a class that uses annotations to define its dependencies:
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
public class MyPart {
@Inject private Logger logger;
// Inject class for database access
@Inject private DatabaseAccessClass dao;
@PostConstruct
public void createControls(Composite parent) {
logger.info("UI will start to build");
Label label = new Label(parent, SWT.NONE);
label.setText("Eclipse 4");
Text text = new Text(parent, SWT.NONE);
text.setText(dao.getNumber());
}
}
This class uses the new operator to create user interface components.
This indicates that the corresponding user interface toolkit is tightly coupled and not intended to be replaced during testing.
In this case, you have intentionally chosen a hard dependency on the UI toolkit.
|
1.3. Where can objects be injected into a class according to JSR330?
Dependency injection can be performed on:
-
the constructor of the class (construction injection)
-
a field (field injection)
-
the parameters of a method (method injection)
It is possible to use dependency injection on static and on non-static fields and methods. Avoiding dependency injection on static fields and methods is a good practice, as it has the following restrictions and can be hard to debug.
-
Static fields will be injected after the first object of the class was created via DI, which means no access to the static field in the constructor
-
Static fields can not be marked as final, otherwise the compiler or the application complains at runtime about them
-
Static methods are called only once after the first instance of the class was created
1.4. Order in which dependency injection is performed on a class
According to the specification the injection is done in the following order:
-
constructor injection
-
field injection
-
method injection
The order in which the methods or fields annotated with @Inject
are called is not defined.
You cannot assume that the methods or fields are called in the order of their declaration in the class.
As fields and method parameters are injected after the constructor is called, you cannot use injected member variables in the constructor. |
2. Java and dependency injection frameworks
You can use dependency injection without any additional framework by providing classes with sufficient constructors or getter and setter methods.
A dependency injection framework simplifies the initialization of the classes with the correct objects.
Two popular dependency injection frameworks are Spring and Google Guice.
The usage of the Spring framework for dependency injection is described in Dependency Injection with the Spring Framework - Tutorial.
Also Eclipse RCP is using dependency injection.
3. Links and Literature
3.2. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting