2021
DI with Spring

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.

AnnotationDescription
@ConfigurationSignals to the container that the class contains beans
@BeanDefines 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.