aopcircuit-breakerresilience4j-retryspring-cloud-circuitbreaker

Resiliency4j CircuitBreaker tried to call circuitBreaker logic in AOP in order to achieve not to call circuit breaker when it is disabled in config


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"
}

Solution

    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.