Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Retry: method annotated with @Recover not being called

I am testing a spring retry, but it seems the recover is not being called. Tried to get it work but it seems exhaustive. I passed to @Recover no argument, Throwable, Exception. Changed retry dependency version, and it seems it is included with aop for spring boot and removed it. Kept getting recover is not being call with the following exception messege.

Request processing failed; nested exception is org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.ArithmeticException: / by zero] with root cause

Any help would be much appreciated

The code i have looks like below.

Configuration class

package hello;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;

@SpringBootApplication
@EnableRetry
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {

            System.out.println("Let's inspect the beans provided by `Spring Boot:");`

            String[] beanNames = ctx.getBeanDefinitionNames();
            Arrays.sort(beanNames);
            for (String beanName : beanNames) {
                System.out.println(beanName);
            }

        };
    }

}

Rest Controller class;

package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @Autowired
    private SomeService service;

    @RequestMapping("/")
    public String hello() {
        String result = service.getInfo();
        return result;
    }

}

Service class is ;

package hello;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class SomeService {

    @Retryable(value = ArithmeticException.class, maxAttempts = 3, `backoff = @Backoff(delay = 3000))`
    public String getInfo() {
        System.out.println("How many time will this be printed?");
        return "Hello" + 4/0;
    }

    @Recover
    public void helpHere(ArithmeticException cause) {
        System.out.println(cause);
        System.out.println("Recovery place!");
    }
}

This is my dependencies list

 <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- tag::actuator[] -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- end::actuator[] -->
    <!-- tag::tests[] -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- end::tests[] -->
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

With try-catch and variety of arguments

@Service
public class SomeService {

    @Retryable(value = {ArithmeticException.class}, maxAttempts = 3, `backoff = @Backoff(delay = 3000))`
    public String getInfo() {
        try {
            System.out.println("How many time will this be printed?");
            return "Hello" + 4/0;
        } catch(ArithmeticException ex) {
            System.out.println("In the arthemetic Exception");
            throw new ArithmeticException();
        }   
    }

    @Recover
    public void helpHere(ArithmeticException cause) {
        System.out.println(cause);
        System.out.println("Recovery place! ArithmeticException");
    }

    @Recover
    public void helpHere(Exception cause ) {
        System.out.println(cause);
        System.out.println("Recovery place! Exception");
    }

    @Recover
    public void helpHere(Throwable cause) {
        System.out.println(cause);
        System.out.println("Recovery place! Exception");
    }

    @Recover
    public void helpHere() {
        System.out.println("Recovery place! Exception");
    }
}

Screen shot of the console

enter image description here

like image 499
Tadele Azanaw Avatar asked May 14 '18 01:05

Tadele Azanaw


2 Answers

I finally got the answer.

For a method annotated with @Recover to be invoked, it has to have the same method argument(plus the exception) and the same return type.

I tested it with different type of exception argument and methods are called if they have more specific exception type. If I have a method like this will be called than one with Exception argument. However, if I have multiple recover methods, only one with the more specific exception argument will be called.

@Recover
public String helpHere(ArithmeticException cause) {

Final code Example

package hello;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class SomeService {

@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 3000))
public String getInfo() {
        try {
        System.out.println("How many time will this be printed?");
        return "Hello" + 4/0;
    } catch(Exception ex) {
            System.out.println("In the arthemetic Exception");
            throw new ArithmeticException();
    }   
}

@Recover
public String helpHere(ArithmeticException cause) {

        System.out.println("Recovery place! ArithmeticException");
        return "Hello";
}
@Recover
public String helpHere(Exception cause ) {

        System.out.println("Recovery place! Exception");
        return "Hello";
}

@Recover
public String helpHere() {
        System.out.println("Recovery place! Exception");
        return "Hello";
}

@Recover
public String helpHere(Throwable cause) {

        System.out.println("Recovery place! Throwable");
        return "Hello";
}

enter image description here

like image 182
Tadele Azanaw Avatar answered Nov 04 '22 09:11

Tadele Azanaw


You should use try-catch to handle it. Here the example

@Retryable(value = ArithmeticException.class, maxAttempts = 5, backoff = @Backoff(delay = 3000))
    public String getInfo() {
        try {
            System.out.println("How many time will this be printed?");
            return "Hello" + 4 / 0;
        } catch (ArithmeticException ex) {
            // will be retried
            throw ex;
        }
    }

throw ex; is a must as it is telling Spring to apply retry handling. With @Recover we define a separate recovery method for ArithmeticException. This allows us to run special recovery code when a retryable method fails with ArithmeticException.

You may refer more on How to handle retry with Spring-Retry ?

Edit

Based on the latest exception,try provide version for spring-retry

 <dependency>
       <groupId>org.springframework.retry</groupId>
       <artifactId>spring-retry</artifactId>
       <version>1.2.1.RELEASE</version>
 </dependency>
like image 21
John Joe Avatar answered Nov 04 '22 09:11

John Joe