Dependency injection (DI). This article describes the concept of dependency injection from a Java perspective.
1. Dependency injection in Java
1.1. An introduction to 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 has a hard dependency and cannot easily be tested in isolation.
The following example shows a class without hard dependencies, as it receives its logger via the constructor:
package com.vogella.tasks.ui.parts;
import java.util.logging.Logger;
public class MyClass {
private final Logger logger;
public MyClass(Logger logger) {
this.logger = logger;
logger.info("This is a log message.");
}
}
|
This is a standard Java class. Its only distinguishing characteristic is that it does not create the objects it depends on. |
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 is decoupled from specific implementations, making it easier to test. For example, such a class can be tested 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, which improves 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 by the Jakarta EE project in the jakarta.inject package.
The following example demonstrates a class that uses annotations to define its dependencies.
import java.util.logging.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
public class MyPart {
@Inject
private Logger logger;
@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 couples the class tightly to the user interface toolkit, which is not intended to be replaced during testing.
This hard dependency on the UI toolkit is intentional.
|
1.3. Injection points in a class defined by JSR 330
Dependency injection can be performed on:
-
the constructor of the class (constructor 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. Avoid dependency injection on static fields and methods, as it has several restrictions and can be difficult to debug:
-
Static fields are injected after the first object of the class has been created via DI, so the constructor cannot access them.
-
Static fields cannot be marked as final, otherwise the compiler or the application complains about them at runtime.
-
Static methods are called only once, after the first instance of the class is 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 fields and methods annotated with @Inject are injected is not defined.
You cannot assume that they are processed in the order of their declaration in the class.
| As fields and methods are injected after the constructor is called, you cannot use injected fields 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 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.
Eclipse RCP uses 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