Spring Boot provides powerful dependency injection features, allowing developers to manage beans effectively. Two key annotations, @Primary
and @Qualifier
, help resolve conflicts when multiple beans of the same type exist. In this article, we’ll explore their usage with a full example.
Understanding @Primary
and @Qualifier
@Primary
: Marks a bean as the default choice when multiple beans of the same type are available.@Qualifier
: Specifies the exact bean to inject, overriding the default behavior of@Primary
.
Example Scenario
Let’s consider a scenario where we have multiple implementations of a PaymentService
interface: CreditCardPaymentService
and PayPalPaymentService
.
Step 1: Define the Interface
public interface PaymentService {
void processPayment(double amount);
}
Step 2: Implement Multiple Beans
import org.springframework.stereotype.Service;
@Service
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
}
}
import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Primary;
@Primary // This makes CreditCardPaymentService the default bean
@Service
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
}
}
Step 3: Inject the Default Bean Using @Primary
import org.springframework.stereotype.Component;
@Component
public class PaymentProcessor {
private final PaymentService paymentService;
public PaymentProcessor(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void makePayment(double amount) {
paymentService.processPayment(amount);
}
}
Since PayPalPaymentService
is marked with @Primary
, it will be injected by default.
Step 4: Override @Primary
Using @Qualifier
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Qualifier;
@Component
public class AlternativePaymentProcessor {
private final PaymentService paymentService;
public AlternativePaymentProcessor(@Qualifier("creditCardPaymentService") PaymentService paymentService) {
this.paymentService = paymentService;
}
public void makePayment(double amount) {
paymentService.processPayment(amount);
}
}
Here, @Qualifier("creditCardPaymentService")
ensures CreditCardPaymentService
is injected instead of the default PayPalPaymentService
.
Step 5: Run the Application
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class PaymentApplication implements CommandLineRunner {
private final PaymentProcessor paymentProcessor;
private final AlternativePaymentProcessor alternativePaymentProcessor;
public PaymentApplication(PaymentProcessor paymentProcessor, AlternativePaymentProcessor alternativePaymentProcessor) {
this.paymentProcessor = paymentProcessor;
this.alternativePaymentProcessor = alternativePaymentProcessor;
}
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
paymentProcessor.makePayment(100.0); // Uses PayPalPaymentService
alternativePaymentProcessor.makePayment(200.0); // Uses CreditCardPaymentService
}
}
Output
Processing PayPal payment of $100.0
Processing credit card payment of $200.0
Conclusion
By using @Primary
, we set a default bean, and @Qualifier
allows us to specify a different implementation when needed. This approach gives flexibility in managing multiple bean implementations within a Spring Boot application.