spring-cloud / spring-cloud-circuitbreaker

Spring Cloud Circuit Breaker API and Implementations
Apache License 2.0
335 stars 110 forks source link

Problems with factory initialisation order in bean resolution #78

Closed arcuri82 closed 3 years ago

arcuri82 commented 4 years ago

Updating a code example from Hystrix to Spring Cloud Circuit Breaker using Resilience4J. It did not work, at first. After couple of hours of debugging, managed to find a (ugly) workaround. Not sure if this is a bug, or intended behaviour. But, in this latter case, the examples and documentation should be improved to avoid others like me having the same issue.

Code snippets in Kotlin. Full code example can be found here

Using Spring Cloud Hoxton.RELEASE.

Assume a custom default configuration, defined in a @SpringBootApplication

    @Bean
    fun globalCustomConfiguration(): Customizer<Resilience4JCircuitBreakerFactory> {

Now, injecting a factory in a Rest controller:

@RestController
class YRest(private val circuitBreakerFactory : Resilience4JCircuitBreakerFactory){ 

      private  var cb : CircuitBreaker? =null

to instantiate the CircuitBreaker cb only once, I thought I could do something like:

   @PostConstruct
    fun init(){
        cb = circuitBreakerFactory.create("circuitBreakerToX")
    }

Unfortunately, that does not work, as by the time it is called, globalCustomConfiguration has not even been instantiated. Adding something like @DependsOn("globalCustomConfiguration") did not help either.

However, the workaround was, before calling cb.run, to instantiate it if null, ie.:

if (cb == null)
            cb = circuitBreakerFactory.create("circuitBreakerToX")

cb!!.run //...
InternetPseudonym commented 4 years ago

spring beans are singletons by default - you dont need to manually implemement a singleton pattern

arcuri82 commented 4 years ago

@InternetPseudonym thanks, but I am not sure I am following your comment here. I know that Spring Beans are singletons by default. The problem here is that Spring seems to inject a bean (circuitBreakerFactory) before it is fully initialized. So wondering if it is a bug in Resilience4JCircuitBreakerFactory, or rather it should be used in a different way

ryanjbaxter commented 4 years ago

What if you create the CB in the constructor?

https://github.com/spring-cloud/spring-cloud-circuitbreaker/pull/56/files#diff-a5678afe8ab680155b2c7ebc5dbae83eR199

arcuri82 commented 4 years ago

@ryanjbaxter thanks for the suggestion. Unfortunately, it does not work :(

ryanjbaxter commented 3 years ago

@arcuri82 I am trying to reproduce this problem but am failing to reproduce it using Hoxton SNAPSHOTs and the link to your sample is not working. Are you still having this problem?

arcuri82 commented 3 years ago

@ryanjbaxter sorry, that was in a branch that I deleted sometime ago, forgetting that it was referenced here...

Anyway, I am still seeing this problem in SpringBoot 2.3.5 with Cloud Hoxton.RELEASE.

You can look at the following YRestTest.kt test, which is currently passing, as I am using a workaround.

However, if you edit the YRest class, by initializing cb in the PostConstruct instead of the REST handler, then you will see the problem

ryanjbaxter commented 3 years ago

Can you try Hoxton.SR9?

If that still doesn't work can you provide a complete, minimal, verifiable sample that reproduces the problem? It should be available as a GitHub (or similar) project or attached to this issue as a zip file.

spring-cloud-issues commented 3 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

arcuri82 commented 3 years ago

with Hoxton.SR9, it works fine ;)

ryanjbaxter commented 3 years ago

Great I will close this then. Thanks!

kihwankim commented 2 years ago

I had the same problem, but I found a solution through the code below.

@RestController
class EndPointController(
    private val circuitBreakerFactory: CircuitBreakerFactory<*, *>
) 

The cause of this problem is that there is no generic type. Therefore, you must enter a wildcard or specific generic type to enable DI If the Kotlin compiler cannot deduce the type from the code you have registered, It will say compile error