← Kursa Dön
📄 Text · 15 min

@Qualifier ve @Primary

Giriş

Bir interface'in birden fazla implementasyonu olduğunda, Spring hangi bean'i enjekte edeceğini bilemez ve NoUniqueBeanDefinitionException fırlatır. @Primary ve @Qualifier bu problemi çözer. Bu derste birden fazla bean arasında seçim yapma stratejilerini öğreneceğiz.

Problem: Birden Fazla Implementasyon

public interface NotificationService {
    void send(String message);
}

@Service
public class EmailNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

@Service
public class SmsNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

@Service
public class OrderService {
    // ❌ HATA! Spring hangisini enjekte edeceğini bilmiyor
    public OrderService(NotificationService notificationService) { }
}
// NoUniqueBeanDefinitionException: expected single matching bean
// but found 2: emailNotificationService, smsNotificationService

@Primary — Varsayılan Seçim

@Primary, bir interface'in birden fazla implementasyonu olduğunda varsayılan bean'i belirler:

@Service
@Primary  // Varsayılan olarak bu enjekte edilir
public class EmailNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

@Service
public class SmsNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

@Service
public class OrderService {
    // ✅ @Primary sayesinde EmailNotificationService enjekte edilir
    public OrderService(NotificationService notificationService) {
        notificationService.send("Sipariş alındı"); // "Email: Sipariş alındı"
    }
}

@Qualifier — Spesifik Seçim

@Qualifier, bean adını belirterek hangi implementasyonun enjekte edileceğini açıkça söyler:

@Service
public class OrderService {
    private final NotificationService emailService;
    private final NotificationService smsService;

    public OrderService(
            @Qualifier("emailNotificationService") NotificationService emailService,
            @Qualifier("smsNotificationService") NotificationService smsService) {
        this.emailService = emailService;
        this.smsService = smsService;
    }

    public void placeOrder(Order order) {
        emailService.send("Sipariş onayı: " + order.getId());
        smsService.send("Siparişiniz alındı: " + order.getId());
    }
}

Özel Bean Adı Verme

@Service("emailSender")  // Özel bean adı
public class EmailNotificationService implements NotificationService { }

@Service("smsSender")
public class SmsNotificationService implements NotificationService { }

// Kullanım
public OrderService(
        @Qualifier("emailSender") NotificationService email,
        @Qualifier("smsSender") NotificationService sms) { }

Custom Qualifier Annotation

Tekrar eden @Qualifier string'leri yerine kendi annotation'ınızı oluşturabilirsiniz:

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface EmailQualifier { }

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface SmsQualifier { }

@Service
@EmailQualifier
public class EmailNotificationService implements NotificationService { }

@Service
@SmsQualifier
public class SmsNotificationService implements NotificationService { }

// Kullanım — type-safe, refactoring-friendly
public OrderService(
        @EmailQualifier NotificationService email,
        @SmsQualifier NotificationService sms) { }

Collection Injection

Bir interface'in tüm implementasyonlarını tek seferde enjekte edebilirsiniz:

@Service
public class NotificationManager {
    private final List<NotificationService> services;

    // Tüm NotificationService bean'leri liste olarak enjekte edilir
    public NotificationManager(List<NotificationService> services) {
        this.services = services;
    }

    public void notifyAll(String message) {
        services.forEach(service -> service.send(message));
    }
}

// Map injection — bean adı key olarak kullanılır
@Service
public class NotificationRouter {
    private final Map<String, NotificationService> serviceMap;

    public NotificationRouter(Map<String, NotificationService> serviceMap) {
        this.serviceMap = serviceMap;
        // {"emailNotificationService": EmailNotification, "smsNotificationService": SmsNotification}
    }

    public void send(String type, String message) {
        NotificationService service = serviceMap.get(type + "NotificationService");
        if (service != null) service.send(message);
    }
}

@Primary vs @Qualifier Karşılaştırma

Özellik@Primary@Qualifier
Tanım yeriBean sınıfındaInjection noktasında
SeçimVarsayılan (implicit)Açık (explicit)
Birden fazlaSadece 1 @Primary olabilirHer injection noktası farklı olabilir
Kullanım"Genelde bunu kullan""Tam olarak bunu kullan"

@Qualifier her zaman @Primary'i override eder.

Best Practices

İpucu: Az sayıda implementasyon varsa @Primary yeterlidir. Çoklu seçim gerekiyorsa @Qualifier kullanın. En temiz yol ise custom qualifier annotation oluşturmaktır.

Uyarı: Bean adlarında string literal kullanmak (@Qualifier("emailService")) kırılgandır. Sınıf adı değişirse çalışmayı durdurur. Custom qualifier annotation daha güvenlidir.

Özet

Birden fazla implementasyon olduğunda @Primary varsayılan bean'i, @Qualifier spesifik bean'i seçer. Collection injection ile tüm implementasyonları alabilirsiniz. Custom qualifier annotation'lar ile type-safe seçim yapabilirsiniz.