Dependency Injection with Spring
Translated from German using ChatGPT.
Date: June 2020
Reading time: 3 minutes
Theory
Reusable, testable, and readable code—Dependency Injection (DI) makes it all possible.
With Dependency Injection, an object receives the necessary dependencies instead of creating them itself. The components are managed in a central container and are not directly coupled with other components.
Example
Without DI
Component A
creates an object that is required by Component D
. Now, the object must be passed all the way to Class D
, a process known as "dependency carrying."
A creates object
A B C D object
With DI
By using Dependency Injection, classes become loosely coupled. D
can use the object without having to "inject" it manually—it is already available.
Container creates the object
Container creates D and injects the object
Container creates C and injects D
Container creates B and injects C
Container creates A and injects B
A B C D object
Benefits
Fewer Dependencies
Dependencies of an object can change quickly. When this happens, modifications to a component may be required.
Example: If the parameters of a method change, the method call must be adjusted. If a component has fewer dependencies on a method, fewer changes are needed.
Cleaner Code
Using DI automatically makes the code cleaner, as the interface clearly shows which dependencies a component has.
Since dependencies are configured externally, components become more reusable.
More Testable
Another major advantage is testability. When testing, a class is not dependent on the functionality of another class because a mock implementation can be injected beforehand.
This mock object can be configured to behave as needed, allowing for isolated and encapsulated unit testing.
Container
I have mentioned a container several times, but what exactly is it?
The Spring IoC container is a framework responsible for instantiating and configuring beans.
This process happens automatically, but it is also possible to manually create such a container.
ApplicationContext context = new ClassPathXmlApplicationContext("appContext.xml");
Implementation
There are different ways to use Dependency Injection (DI).
To understand the examples, it is important to recognize the annotations.
Annotation | Description |
---|---|
@Configuration | Signals to the container that the class contains beans |
@Bean | Defines that the method returns a bean |
Constructor
The class does not need to create a new object. The team only needs to inject the player.
public class Team {
private Player player;
public Team(Player player) {
this.player = player;
}
}
Configuration
All required beans are placed in a configuration file unless they already exist there.
@Configuration
public class AppConfig {
@Bean
public Player player1() {
return new PlayerImpl1();
}
@Bean
public Team team() {
return new Team(player1());
}
}
It theoretically doesn't matter where you write them. However, it's important to maintain the cleanliness of the project, so all beans should be defined in a logical location.
Setter
This variant is commonly used to define optional classes, as it doesn't require a mandatory connection to be established.
@Bean
public Team team() {
Team team = new Team();
team.setPlayer(player1());
return team;
}
Interface
An interface ensures that the component is injected.
public interface Team {
void injiziere(final Coach coach);
}
public class Basel implements Team {
private final Coach coach;
public void injiziere(final Coach coach) {
this.player = player;
}
}
public class Team {
public void methode() {
public final Coach coach = ... ;
public final Team team = ... ;
team.injiziere(player);
}
}
Autowired
Autowired looks very clean and is therefore my favorite. You simply need to specify the desired object with @Autowired
.
public class Team {
@Autowired
private Player player;
}
Conclusion
Of course, there are also disadvantages. If you don't understand the principle of DI, it can be difficult to follow what is happening in the code. You also need to be familiar with the annotations first.
It is also not ideal to be dependent on a framework.
Additionally, DI can sometimes interfere with the IDE's automation. For example, Visual Studio Code might not be able to display a preview of a reference.
However, in my opinion, the benefits outweigh the disadvantages.