Card 04/ 07
All 7 cards
GotchaDifficulty: Intermediate1 min
Two Beans, One Interface, No Winner
Add a second implementation of NotificationSender, and the application that was starting fine 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,smsSenderThe trigger is reproducible and the mistake is easy to make without noticing: nothing about adding EmailNotificationSender touched RideAlertService, and yet RideAlertService is what refuses to build. Spring has two candidates for one constructor parameter and no rule for choosing between them, so it refuses to guess.
Two fixes, for two different situations. @Primary on one implementation says "use this one unless told otherwise" — right when one sender genuinely is the default. @Qualifier("emailSender") on the constructor parameter says "this specific caller wants this specific bean" — right when the choice depends on who's asking, not on which is generally preferred.
// @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) { /* ... */ }
}--- @Primary picks smsSender ---
sms: your ride has arrived
--- @Qualifier picks emailSender ---
email: your ride has arrived