Search code examples
javarestspring-bootkotlinspring-retry

Kotlin with Spring-Retry the @Recover not called


I'm having a problem using spring boot and spring retry, the @Recover annotation method is not being called when making all the possible attempts.

I'm using spring with kotlin.

My Application servlet container:

class ServletInitializer : SpringBootServletInitializer() {

    override fun configure(application: SpringApplicationBuilder) : SpringApplicationBuilder {
        return application.sources(SecurityServicesApplication::class.java)
    }
}

My Configuration

import org.springframework.context.annotation.Configuration import org.springframework.retry.annotation.EnableRetry

@Configuration
@EnableRetry
class RetryConfig

Updated

My Service

import security.commons.exception.SecurityException
import org.apache.commons.lang3.exception.ExceptionUtils
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Recover
import org.springframework.retry.annotation.Retryable
import org.springframework.security.oauth2.client.OAuth2RestTemplate
import org.springframework.security.oauth2.common.OAuth2AccessToken
import org.springframework.stereotype.Service
import java.net.ConnectException

@Service
class AuthorizationServerTokenRequester {

    val log = LoggerFactory.getLogger(Oauth2Service::class.java)!!

    @Value("\${accessTokenUri}")
    private val accessTokenUri: String? = null


    @Retryable(
            value = [SecurityException::class],
            maxAttemptsExpression = "\${server.oauthclient.retry.maxAttempts}",
            backoff = Backoff(delayExpression = "\${server.oauthclient.retry.delay}"))
    fun token(oauth2RestTemplate: OAuth2RestTemplate): OAuth2AccessToken? {
        try {

            return oauth2RestTemplate.accessToken
        } catch (ex: Exception) {
            if (ExceptionUtils.getRootCause(ex) is ConnectException) {
                log.error("trying again....")
            }
            throw com.security.commons.exception.SecurityException("")
        }
    }

    @Recover
    fun recover(ex: SecurityException) {
        print("##############################################################################################sssss# SecurityException")
    }
}

My Error Log:

2018-08-10 11:28:41.802 DEBUG 40168 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : 
    Written [{timestamp=Fri Aug 10 11:28:41 BRT 2018, status=500, error=Internal Server Error, exception=org.springframework.retry.ExhaustedRetryException, 
              message=Cannot locate recovery method; nested exception is security.commons.exception.SecurityException: , 
              path=/security/api/v1/oauth2/token}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@21e2d8f6]

Solution [with the help of @Gary Russell]

The return of the @Recover method must be the same as the @Retryable method

fun recover(ex: S@RecoverecurityException, oauth2RestTemplate: OAuth2RestTemplate ) : OAuth2AccessToken {
    print("##############################################################################################sssss# SecurityException")
    throw br.com.totvs.security.commons.exception.SecurityException("")
}

Solution

  • @Recover methods must have the same return type as the @Retryable method.

    EDIT

    This works fine for me...

    @SpringBootApplication
    @EnableRetry
    public class So51787951Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So51787951Application.class, args);
        }
    
        @Bean
        public ApplicationRunner runner(Foo foo) {
            return args -> {
                try {
                    foo.foo("bar");
                }
                catch (Exception e) {
    
                }
            };
        }
    
        @Bean
        public Foo foo() {
            return new Foo();
        }
    
    }
    

    and

    open class Foo {
    
        @Retryable(maxAttempts = 3)
        open fun foo(data: String) :String? {
            println(data)
            throw Exception("foo")
        }
    
        @Recover
        open fun rec(data: Exception) :String? {
            println("Recovered")
            return null;
        }
    
    }
    

    and

    bar
    bar
    bar
    Recovered
    

    Note that I had to make Foo and its methods open because Spring creates a CGLIB proxy subclass since there is no interface.