Conditionally I want to switch the circuit breaker switch off/on by setting spring.cloud.circuitbreaker.resilience4j.enabled=false. My logic should stay intact from circuit-breaker logic.

I tried using the below demo example to extend to my requirements, I am trying to bind circuit breaker call on target method based on circuit breaker flag spring.cloud.circuitbreaker.resilience4j.enabled=true in application.property, true and false case. There could be a simpler way to achieve this, help me if any other solution than what I tried.

Example: spring cloud circuit-breaker-resiliency4j example

Tried calling happy path - Work fine when there is no exception [response comes within 3 seconds as time limiter set to 3seconds in bean creation]

application.properties:

spring.cloud.circuitbreaker.resilience4j.enabled=true
spring.cloud.config.enabled=false
spring.cloud.config.import-check.enabled=false
spring.main.allow-bean-definition-overriding=true

Controller:

@GetMapping("/delay/{seconds}")
public Map delay(@PathVariable int seconds) {
   return mockService.delay(seconds);
}

MockService:

@ApplyCircuitBreaker
public Map delay(int seconds) {
    return rest.getForObject("https://httpbin.org/delay/" + seconds, Map.class);
}

Config class:

@Configuration
@ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled"}, matchIfMissing = true)
public class ResiliencyConfig {

    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
            .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
            .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
            .build());
    }
}

ApplyCircuitBreaker - Custom annotation to Apply circuit breaker only for required methods:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApplyCircuitBreaker {
}

AOP: CircuitBreakerAroundAspect:

@Aspect
@Component
@ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
    "spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)
public class CircuitBreakerAroundAspect {

    @Autowired
    CircuitBreakerFactory circuitBreakerFactory;

    @Around("@annotation(com.ravibeli.circuitbreaker.aspects.ApplyCircuitBreaker)")
    public Object aroundAdvice(final ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Arguments passed to method are: {}", Arrays.toString(joinPoint.getArgs()));
        AtomicReference<Map<String, String>> fallback = new AtomicReference<>();
        Object proceed = circuitBreakerFactory.create(joinPoint.getSignature().toString())
            .run(() -> {
                try {
                    log.info("Inside CircuitBreaker logic in Aspect");
                    return joinPoint.proceed();
                } catch (Throwable t) {
                    log.error(t.getMessage());
                }
                return null;
            }, Throwable::getMessage);
        log.info("Result from method is: {}", proceed);
        return proceed;
    }
}

My requirement: circuitBreakerFactory.create(joinPoint.getSignature().toString()) .run(() -> ....) at this line, when target method throws exception, controll should go to fallback mechanism call. Since joinPoint.proceed() throws exception, it is forcing to handle exception - So I am doing wrong here, need suggestion to fix this to solve the requirement.

Error log:

{
    "timestamp": "2021-07-10T01:33:10.558+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "trace": "java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')\r\n\tat com.ravibeli.circuitbreaker.service.MockService$$EnhancerBySpringCGLIB$$3e293bd0.delay(<generated>)\r\n\tat com.ravibeli.circuitbreaker.controllers.DemoController.delay(DemoController.java:53)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
    "message": "class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap')",
    "path": "/delay/3"
}
3

There are 3 answers

0
Najeeb Arif On BEST ANSWER
  1. For enabling the circuit breaker dynamically you can use Profiles or Externalized Configuration (preferred approach would be to use Profiles and you can google more about them)
  2. As far as your aspect's code goes, it looks and runs fine for me. Link to Code. It would be better if you could share the link to the code-base so that the issue can be investigated a bit further. Nevertheless, it seems a minor issue.
0
ravibeli On

Thanks, guys for your comments, got the simple idea to fix this. I resolved it with a custom factory implementation to make enable/disable feature working.

My GitHub example code: spring-cloud-resiliency4j

4
Eric On

You seem to be asking a couple different questions here.

The title seems to be asking why the aspect is still present when spring.cloud.circuitbreaker.resilience4j.enabled=false

The problem is with your conditional

@ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
    "spring.cloud.circuitbreaker.resilience4j.reactive.enabled" }, matchIfMissing = true)

It's simply requiring the property be present, it's not checking what it's set to. You need to set havingValue=true as well.

That said, I would strongly suggest not making your own pointcut for circuit breakers. Use the annotations provided by Resiliancy4j and just specify the fallback method there. I would expect that to clear up any other issues you're having with fallbacks.

@Bulkhead(name = 'myService', fallbackMethod = "myFallback")
@CircuitBreaker(name = 'myService', fallbackMethod = "myFallback")
@RateLimiter(name = 'myService', fallbackMethod = "myFallback")
@TimeLimiter(name = 'myService', fallbackMethod = "myFallback")