Dependency Injection (DI) ist ein zentrales Konzept im Spring Framework und bildet die Grundlage für die lose Kopplung zwischen Komponenten in einer Anwendung. DI ist eine spezielle Form der Inversion of Control (IoC), bei der Abhängigkeiten von Komponenten von außen bereitgestellt werden, anstatt dass die Komponenten selbst ihre Abhängigkeiten instanziieren. Dieses Prinzip ermöglicht es, flexiblen, wiederverwendbaren und testbaren Code zu schreiben.
In diesem Kapitel erläutern wir die Prinzipien von Dependency Injection, wie sie in Spring Boot implementiert wird, und zeigen anhand von Beispielen die verschiedenen Arten der DI.
Dependency Injection ist ein Entwurfsmuster, bei dem eine Klasse ihre Abhängigkeiten nicht selbst erstellt, sondern diese von einem IoC-Container, in unserem Fall Spring, bereitgestellt werden. Dies hat mehrere Vorteile:
In Spring Boot wird DI durch den ApplicationContext
realisiert, der die Aufgabe übernimmt, Beans zu verwalten und die
Abhängigkeiten zu injizieren.
Es gibt verschiedene Arten der Dependency Injection in Spring:
Die konstruktorbasierte Injection wird als die empfohlene Methode angesehen, da sie sicherstellt, dass alle Abhängigkeiten bereits bei der Instanziierung der Bean verfügbar sind. Es handelt sich hierbei um die sauberste Form der DI, da die Abhängigkeiten explizit über den Konstruktor definiert werden.
Beispiel: Konstruktorbasierte Dependency Injection
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final PaymentService paymentService;
// Konstruktor-Injection
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder() {
paymentService.processPayment();
}
}In diesem Beispiel wird die PaymentService-Abhängigkeit
durch den Konstruktor der OrderService-Bean injiziert.
Spring sorgt automatisch dafür, dass die passende Bean in den
Konstruktor injiziert wird.
Bei der Setter-basierten Injection werden die Abhängigkeiten über Setter-Methoden bereitgestellt. Diese Methode kann nützlich sein, wenn nicht alle Abhängigkeiten zwingend erforderlich sind oder wenn sie nach der Instanziierung der Bean verändert werden sollen.
Beispiel: Setter-basierte Dependency Injection
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private PaymentService paymentService;
// Setter-Injection
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder() {
paymentService.processPayment();
}
}Hier wird die PaymentService-Abhängigkeit durch eine
Setter-Methode injiziert. Spring ruft die Setter-Methode auf, um die
Abhängigkeit zu setzen.
Die feldbasierte Injection ist die einfachste Methode der DI, bei der die Abhängigkeit direkt in einem Feld injiziert wird. Es erfordert keine Setter-Methoden oder Konstruktoren, was den Code weniger ausführlich, aber auch weniger testbar macht.
Beispiel: Feldbasierte Dependency Injection
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
public void processOrder() {
paymentService.processPayment();
}
}In diesem Beispiel wird das Feld paymentService direkt
mit der @Autowired-Annotation versehen, wodurch Spring die
passende Bean injiziert.
Die konstruktorbasierte Injection gilt als die beste Praxis, da sie sicherstellt, dass die Abhängigkeiten bei der Instanziierung vollständig bereitgestellt werden und die Klasse somit sofort in einem gültigen Zustand ist. Sie erleichtert zudem das Testen, da die Abhängigkeiten explizit im Konstruktor übergeben werden.
Die @Autowired-Annotation ist das zentrale Werkzeug, um
Spring mitzuteilen, dass eine bestimmte Abhängigkeit injiziert werden
soll. Sie kann in allen drei Arten der Injection verwendet werden:
Konstruktoren, Setter und Feldern. Wenn keine explizite Bean definiert
wurde, versucht Spring, eine passende Bean basierend auf dem Typ zu
finden.
Manchmal kann es notwendig sein, eine Abhängigkeit als optional zu
markieren. Das bedeutet, dass die Bean nicht zwingend existieren muss.
Dies kann mit @Autowired(required = false) realisiert
werden:
Beispiel: Optionale Abhängigkeit
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired(required = false)
private DiscountService discountService;
public void processOrder() {
if (discountService != null) {
discountService.applyDiscount();
} else {
System.out.println("Kein Discount-Service verfügbar.");
}
}
}In diesem Beispiel wird der DiscountService nur
injiziert, wenn er verfügbar ist. Wenn keine Bean vorhanden ist, bleibt
das Feld discountService null, was die Anwendung handhaben
muss.
@QualifierWenn es mehrere Beans desselben Typs gibt, muss durch Verwendung von
@Qualifier spezifiziert werden, welche Bean injiziert
werden soll. Dies ist notwendig, um Mehrdeutigkeiten zu vermeiden.
Beispiel: Verwendung von @Qualifier
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired
public OrderService(@Qualifier("paypalService") PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder() {
paymentService.processPayment();
}
}In diesem Beispiel wird durch den @Qualifier explizit
die paypalService-Bean als Abhängigkeit angegeben.
Spring kümmert sich nicht nur um die Injection von Beans, sondern auch um deren gesamten Lebenszyklus. Spring beans durchlaufen mehrere Phasen, einschließlich Instanziierung, Initialisierung und Zerstörung.
Beans können über @PostConstruct oder das Interface
InitializingBean initialisiert werden, nachdem alle
Abhängigkeiten injiziert wurden.
Beispiel: Initialisierung mit
@PostConstruct
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
@PostConstruct
public void init() {
System.out.println("OrderService wurde initialisiert.");
}
}Beans können über @PreDestroy oder das Interface
DisposableBean gereinigt oder zerstört werden.
Beispiel: Zerstörung mit
@PreDestroy
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@PreDestroy
public void destroy() {
System.out.println("OrderService wird zerstört.");
}
}Die Verwendung von Dependency Injection in Spring Boot bringt zahlreiche Vorteile mit sich:
Dependency Injection ist ein fundamentales Konzept im Spring Framework und ermöglicht es Entwicklern, lose gekoppelte, flexible und testbare Anwendungen zu entwickeln. Durch die Verwendung von DI wird der Code sauberer und leichter wartbar, da Abhängigkeiten nicht explizit in den Klassen selbst erstellt werden müssen. Die verschiedenen Formen der DI – Konstruktor, Setter und Felder – bieten Entwicklern die Flexibilität, den besten Ansatz für ihre Anwendung zu wählen, wobei die Konstruktorinjektion als beste Praxis angesehen wird.