Card 04/ 07
Ambiguous Beans: Two Implementations of One Interface
Adding a second implementation stops the application from starting, and @Primary or @Qualifier fixes it.
When two beans implement the interface a constructor asks for, Spring has no rule for choosing between them, so it refuses to start the application instead of guessing. @Primary (on a bean: use me by default) and @Qualifier (on a parameter: use the bean with this name) are the two ways to tell it which one to use.
Add a second implementation of NotificationSender, and an application that started a moment ago refuses to start at all:
interface NotificationSender { void send(String message); }
@Component("smsSender")
class SmsNotificationSender implements NotificationSender { /* ... */ }
@Component("emailSender")
class EmailNotificationSender implements NotificationSender { /* ... */ }
@Service
class RideAlertService {
private final NotificationSender sender;
RideAlertService(NotificationSender sender) { this.sender = sender; }
}UnsatisfiedDependencyException: Error creating bean with name 'rideAlertService': ...
No qualifying bean of type 'rides.NotificationSender' available:
expected single matching bean but found 2: emailSender,smsSenderNothing about adding EmailNotificationSender touched RideAlertService, and yet RideAlertService is the bean that fails to build. Its constructor asks for one NotificationSender, the container has two candidates, and it will not guess. Because the container builds every singleton (a bean it keeps exactly one of, which is the default) at startup, the mistake shows up at once, before a single message goes out through the wrong channel.
There are two fixes, for two situations. @Primary on one implementation says "use this one unless told otherwise", which is right when one sender really is the default. @Qualifier("emailSender") on a constructor parameter says "this caller wants this bean", which is right when the choice depends on who is asking.
// @Primary: pick a default
@Component("smsSender")
@Primary
class SmsNotificationSender implements NotificationSender { /* ... */ }
// @Qualifier: this caller wants a specific one
@Service
class FixedByQualifierService {
FixedByQualifierService(@Qualifier("emailSender") NotificationSender sender) { /* ... */ }
}Running each fix in turn:
--- @Primary picks smsSender ---
sms: your ride has arrived
--- @Qualifier picks emailSender ---
email: your ride has arrived← → move through sections and cards