Search code examples
javaspring-bootcircuit-breakerspring-cloud-circuitbreaker

Annotation @Qualifier does not working properly and said: class required a single bean, but 2 were found:


I want to implement CircuitBreaker pattern in an spring boot application.I have two APIs and each API has its own configuration. I defined these two configuration methods in application class. Also, in application class I defined a Spring Bean of type CustomCircuitBreakerFactory. This type is a custom class and will be created to manage and configure circuit breakers for APIs. This spring bean takes three parameters in its constructor:

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;

import org.springframework.context.annotation.Bean;

import java.time.Duration;
import java.util.List;


@SpringBootApplication(scanBasePackages = {"com.company.*"})
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }

    @Bean
    @Qualifier("api1CircuitBreakerConfig")
    public CircuitBreakerConfig api1CircuitBreakerConfig() {

        return CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofSeconds(10))
            .permittedNumberOfCallsInHalfOpenState(10)
            .slidingWindowSize(100)
            .minimumNumberOfCalls(20)
            .recordExceptions(RuntimeException.class, CallNotPermittedException.class)
            .build();
    }

    @Bean
    @Qualifier("api2CircuitBreakerConfig")
    public CircuitBreakerConfig api2CircuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
            .failureRateThreshold(40)
            .waitDurationInOpenState(Duration.ofSeconds(10))
            .permittedNumberOfCallsInHalfOpenState(10)
            .slidingWindowSize(150)
            .minimumNumberOfCalls(30)
            .recordExceptions(RuntimeException.class, CallNotPermittedException.class)
            .build();

    }

    @Bean
    public CustomCircuitBreakerFactory customCircuitBreakerFactory(
        ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory,
        CircuitBreakerConfig api1CircuitBreakerConfig,
        CircuitBreakerConfig api2CircuitBreakerConfig) {
        return new CustomCircuitBreakerFactory(circuitBreakerFactory, api1CircuitBreakerConfig, api2CircuitBreakerConfig);
    }
}


It is the implementation of CustomCircuitBreakerFactory. It is an component class to configure and manage the circuit breakers. It takes three parameters in its constructor has two methods to create and configure instances of circuit breakers for each of underlying APIs.

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class CustomCircuitBreakerFactory {

    private final ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory;
    private final CircuitBreakerConfig api1CircuitBreakerConfig;
    private final CircuitBreakerConfig api2CircuitBreakerConfig;


    public CustomCircuitBreakerFactory(
        ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory,
        @Qualifier("api1CircuitBreakerConfig") CircuitBreakerConfig api1CircuitBreakerConfig,
        @Qualifier("api2CircuitBreakerConfig") CircuitBreakerConfig api2CircuitBreakerConfig) {
        this.circuitBreakerFactory = circuitBreakerFactory;
        this.api1CircuitBreakerConfig= api1CircuitBreakerConfig;
        this.api2CircuitBreakerConfig= api2CircuitBreakerConfig;
    }


    public CircuitBreaker createApi1CircuitBreaker() {
        return (CircuitBreaker) circuitBreakerFactory.create("api1");
    }

    public CircuitBreaker createApi2CircuitBreaker() {
        return (CircuitBreaker) circuitBreakerFactory.create("api2");
    }
}

When I run the application I got this error: Parameter 1 of method customCircuitBreakerFactory in ApiApplication required a single bean, but 2 were found: - api1CircuitBreakerConfig: defined by method 'api1CircuitBreakerConfig' in ApiFacadeApplication - api2CircuitBreakerConfig: defined by method 'api2CircuitBreakerConfig' in ApiFacadeApplication I used @Qualifier but it does not work.

I used @Qualifier but it does not work.


Solution

  • There are several issues in your code

    1. @Qualifier has effect when bean is injected but not provided. Setting custom qualifier to a bean is possible with annotations that create a bean: @Bean, @Service, @Component or similar.
    2. Both @Component and @Bean add a bean to the Spring context. You should use either but not both.
    3. @Qualifier should be used on every place where autowiring process takes place.

    So, steps to make your code work:

    1. Remove @Qualifier on api1CircuitBreakerConfig() and api2CircuitBreakerConfig() and instead add names to @Bean annotations. Note that by default Spring uses qualifier equal to name of the method/class, so it's redundant in your case.
    2. Remove @Component from CustomCircuitBreakerFactory
    3. Add @Qualifer annotations to parameters of ApiApplication.customCircuitBreakerFactory