28 Qualifiers

Spring Boot bietet zahlreiche Möglichkeiten, um Abhängigkeiten in Beans zu verwalten und automatisch zu verdrahten. In den meisten Fällen genügt es, eine einfache Autowiring-Strategie mit @Autowired zu verwenden, um Abhängigkeiten zwischen Beans zu lösen. In komplexeren Szenarien, in denen mehrere Implementierungen eines bestimmten Interfaces oder Typs existieren, ist jedoch eine zusätzliche Spezifizierung erforderlich. Hier kommen Qualifiers ins Spiel. Qualifiers ermöglichen es Ihnen, genau anzugeben, welche Bean in einer solchen Situation injiziert werden soll.

28.1 Problemstellung: Mehrdeutige Abhängigkeiten

In vielen realen Anwendungen haben Sie mehrere Implementierungen einer bestimmten Schnittstelle oder eines Typs. Ohne Qualifier würde Spring nicht wissen, welche Implementierung zu verwenden ist, und es würde zu einer NoUniqueBeanDefinitionException kommen.

Beispiel: Mehrere Implementierungen

public interface PaymentService {
    void processPayment();
}

@Component
public class PaypalService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Verarbeitung der Zahlung über PayPal.");
    }
}

@Component
public class CreditCardService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Verarbeitung der Zahlung über Kreditkarte.");
    }
}

In diesem Beispiel gibt es zwei Implementierungen des PaymentService-Interfaces: PaypalService und CreditCardService. Wenn Sie nun versuchen, den PaymentService zu injizieren, wird Spring nicht wissen, welche Implementierung verwendet werden soll, da beide Beans desselben Typs vorhanden sind.

@Component
public class OrderService {

    @Autowired
    private PaymentService paymentService; // Mehrdeutigkeit!
}

Spring würde in diesem Fall eine Ausnahme werfen, da es zwei Kandidaten für die Injection gibt.

28.2 Lösung: Qualifiers

Mit der Annotation @Qualifier können Sie explizit angeben, welche Implementierung Sie injizieren möchten, wenn mehrere Beans desselben Typs vorhanden sind.

28.2.1 Verwendung von @Qualifier

Um eine spezifische Bean zu injizieren, verwenden Sie den @Qualifier, indem Sie den Namen der gewünschten Bean angeben. In unserem obigen Beispiel könnten wir angeben, ob wir PaypalService oder CreditCardService verwenden möchten.

Beispiel: Qualifier verwenden

@Component
public class OrderService {

    private final PaymentService paymentService;

    @Autowired
    public OrderService(@Qualifier("paypalService") PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void processOrder() {
        paymentService.processPayment();
    }
}

Hier geben wir explizit an, dass die paypalService-Bean verwendet werden soll. Spring wird nun die PaypalService-Bean injizieren, wenn der OrderService instanziiert wird.

28.2.2 Bean-Namen und Qualifiers

Spring verwendet standardmäßig den Klassennamen mit kleinem Anfangsbuchstaben als Bean-Namen. Dies bedeutet, dass die PaypalService-Bean automatisch den Namen paypalService erhält, und die CreditCardService-Bean den Namen creditCardService. Sie können diesen Namen jedoch überschreiben, indem Sie den Bean-Namen explizit mit der @Component-Annotation festlegen:

@Component("customPaypalService")
public class PaypalService implements PaymentService {
    // Implementierung
}

In diesem Fall müssten Sie den Qualifier mit dem benutzerdefinierten Bean-Namen verwenden:

@Autowired
@Qualifier("customPaypalService")
private PaymentService paymentService;

28.3 Qualifiers mit List und Map

Es ist auch möglich, mehrere Beans desselben Typs zu injizieren und sie als List oder Map zu verwalten. Dies ist besonders nützlich, wenn Sie mit einer Sammlung von Beans arbeiten möchten, ohne explizit einen Qualifier für jede Bean anzugeben.

28.3.1 Injection als Liste

Wenn Sie alle Beans eines bestimmten Typs in eine Liste injizieren möchten, können Sie dies mit @Autowired tun:

Beispiel: Alle Beans desselben Typs injizieren

@Component
public class PaymentProcessor {

    private final List<PaymentService> paymentServices;

    @Autowired
    public PaymentProcessor(List<PaymentService> paymentServices) {
        this.paymentServices = paymentServices;
    }

    public void processAllPayments() {
        for (PaymentService paymentService : paymentServices) {
            paymentService.processPayment();
        }
    }
}

In diesem Beispiel wird eine Liste aller PaymentService-Implementierungen injiziert und jede Implementierung wird nacheinander aufgerufen.

28.3.2 Injection als Map

Sie können auch Beans mit ihrem Namen in eine Map injizieren. Der Bean-Name wird dann als Schlüssel verwendet.

Beispiel: Beans als Map injizieren

@Component
public class PaymentProcessor {

    private final Map<String, PaymentService> paymentServiceMap;

    @Autowired
    public PaymentProcessor(Map<String, PaymentService> paymentServiceMap) {
        this.paymentServiceMap = paymentServiceMap;
    }

    public void processPayment(String paymentType) {
        PaymentService paymentService = paymentServiceMap.get(paymentType);
        if (paymentService != null) {
            paymentService.processPayment();
        } else {
            System.out.println("Ungültiger Zahlungstyp.");
        }
    }
}

In diesem Beispiel können Sie den Namen der Bean als Schlüssel verwenden, um eine bestimmte Implementierung des PaymentService zur Laufzeit auszuwählen.

28.4 Custom Qualifiers

Zusätzlich zur Verwendung des @Qualifier mit Bean-Namen können Sie auch benutzerdefinierte Qualifier erstellen, um Beans weiter zu differenzieren. Dies ist nützlich, wenn Sie eine flexiblere und spezifischere Zuordnung benötigen.

28.4.1 Erstellen eines benutzerdefinierten Qualifiers

Sie können einen benutzerdefinierten Qualifier erstellen, indem Sie eine Annotation definieren, die mit @Qualifier annotiert ist.

Beispiel: Custom Qualifier erstellen

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PaymentType {
    String value();
}

Verwenden Sie diesen Qualifier, um spezifische Beans zu kennzeichnen:

@Component
@PaymentType("paypal")
public class PaypalService implements PaymentService {
    // Implementierung
}

@Component
@PaymentType("creditCard")
public class CreditCardService implements PaymentService {
    // Implementierung
}

Sie können dann den benutzerdefinierten Qualifier verwenden, um eine spezifische Implementierung zu injizieren:

@Autowired
@PaymentType("paypal")
private PaymentService paymentService;

28.5 Best Practices für die Verwendung von Qualifiers

  1. Vermeiden Sie unnötige Komplexität: Verwenden Sie Qualifiers nur dann, wenn es mehrere Implementierungen desselben Typs gibt und Sie gezielt eine auswählen müssen. Zu viele Qualifiers können die Wartbarkeit des Codes erschweren.

  2. Namen klar definieren: Verwenden Sie aussagekräftige und gut dokumentierte Bean-Namen oder benutzerdefinierte Qualifier, um Verwechslungen zu vermeiden.

  3. Verwalten Sie Collections effektiv: Wenn Sie alle Beans eines Typs benötigen, verwenden Sie Listen oder Maps, um den Code übersichtlicher und flexibler zu gestalten.

  4. Custom Qualifier sparsam einsetzen: Benutzerdefinierte Qualifier sollten nur verwendet werden, wenn herkömmliche Qualifiers nicht ausreichen, um die Abhängigkeiten zu lösen. Einfache Namensqualifiers sind oft ausreichend.

28.6 tl;dr

@Qualifier ist ein leistungsfähiges Werkzeug in Spring Boot, das es ermöglicht, mehrdeutige Abhängigkeiten zu lösen und genau zu definieren, welche Bean in einer bestimmten Situation injiziert werden soll. Durch den Einsatz von Qualifiers können Sie komplexe Anwendungen mit mehreren Implementierungen desselben Typs übersichtlich und wartbar gestalten. Es ist wichtig, Qualifiers effizient einzusetzen, um die Wartbarkeit und Flexibilität Ihrer Anwendung zu gewährleisten.