Retrying Method calls in generic way
Asked Answered
V

7

12

My Java application requires a retry logic on remote calls failures. These remote calls are:

  • scattered all over the application
  • pertain to different Remote Service classes.

Also, the retry logic may have varying retry interval and varying retry attempts.

I need a generic retry() implementation which can make appropriate method calls depending on from where it is called. Below is a simple code illustration of I am looking for. I know we can attempt to do this using java reflection, but, is there a framework or an open source available somewhere which is read-to-use?

try {
 ClassA objA = remoteServiceA.call(paramA1, paramA2, ...);
} catch (Exception e){
 ClassA objA = (ClassA)retry(remoteService, listOfParams, ..); // generic method call
}
..

try {
 ClassB objB = remoteServiceB.call(paramB1, paramB2, ...);
} catch (Exception e){
 ClassA objB = (ClassB)retry(remoteService, listOfParams, ..); // generic method call
}
Volkslied answered 6/6, 2012 at 20:28 Comment(0)
F
12

As already suggested, you should use AOP and Java annotations. I would recommend a read-made mechanism from jcabi-aspects (I'm a developer):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
  return url.openConnection().getContent();
}

Read also this blog post: http://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html

Update: Check RetryFunc from Cactoos.

Flora answered 3/2, 2013 at 8:30 Comment(0)
A
1

This is a book example of where (or in general) can be used, see 8.2.7 Example in Spring documentation and 5 Reasons Java Developers Should Learn and Use AspectJ.

Basically an aspect intercepts all calls to given methods (specified using annotation, naming convention, whatever) and retries.

Arel answered 6/6, 2012 at 20:35 Comment(0)
I
1

Assume you have a method, that need to retied at every 500ms and upto 5 times. Current class:

public class RemoteCaller{
    Service serviceCaller;
    public void remoteCall(String message) {
                serviceCaller.updateDetails( this.message);
                return null;
    }
}

Modified approach:

public class RetriableHelper<T> implements Callable<T> {

    private Callable<T> task;

    private int numberOfRetries;

    private int numberOfTriesLeft;

    private long timeToWait;


    public RetriableHelper(int numberOfRetries, long timeToWait, Callable<T> task) {
        this.numberOfRetries = numberOfRetries;
        numberOfTriesLeft = numberOfRetries;
        this.timeToWait = timeToWait;
        this.task = task;
    }

    public T call() throws Exception {
        while (true) {
            try {
                return task.call();
            } catch (InterruptedException e) {
                throw e;
            } catch (CancellationException e) {
                throw e;
            } catch (Exception e) {
                numberOfTriesLeft--;
                if (numberOfTriesLeft == 0) {
                    throw e; 
                }
                Thread.sleep(timeToWait);
            }
        }
    }
}


Backend system/remote call class:

public class RemoteCaller{

    Service serviceCaller;

    public void remoteCall(String message) {

        class RemoteCallable implements Callable<Void> {
            String message;
            public RemoteCallable( String message)
            {
                this.message = message;
            }
            public Void call() throws Exception{
                serviceCaller.updateDetails( this.message);
                return null;
            }
        }


        RetriableHelper<Void> retriableHelper = new RetriableHelper<Void>(5, 500, new RemoteCallable( message));
        try {
            retriableHelper.call();
        } catch (Exception e) {
            throw e;
        } 
     }
}
Ipecac answered 30/6, 2015 at 14:51 Comment(0)
G
1

enter link description here Spring has a retry annotation which servers the purpose

Step 1: Add following dependency to your POM

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.1.5.RELEASE</version>
</dependency>


Step 2: Enabling Spring Retry

To enable Spring Retry in an application, we need to add the @EnableRetry annotation to our @Configuration class:

Ex:

@Configuration
@EnableRetry
public class AppConfig { ... }


Step 3: To add retry functionality to methods, @Retryable can be used:

Ex: 

@Service
public interface MyService {
    @Retryable(
      value = { SQLException.class }, 
      maxAttempts = 2,
      backoff = @Backoff(delay = 5000))
    void retryService(String sql) throws SQLException;
    ...
}


Step 4.The @Recover annotation is used to define a separate recovery method when a @Retryable method fails with a specified exception:

Ex: 

@Service
public interface MyService {
    ...
    @Recover
    void recover(SQLException e, String sql);
}


See Url for more details : http://www.baeldung.com/spring-retry
Goalkeeper answered 23/5, 2018 at 17:58 Comment(0)
G
0

where do you get the services from? use a factory to Proxy the service you get from the original factory. The proxy can then implement the retry transparently. See the java Proxy/ProxyGenerators in reflection.

Greenlaw answered 6/6, 2012 at 20:31 Comment(0)
T
0

If you are using spring , then better go with Aspects.
Otherwise, below sample solution can work:

public class Test
{
    public static void main(String[] args) throws Exception
    {
        Test test = new Test();
        test.toRunFirst("Hello! This is normal invocation");
        runWithRetry(test, "toRunFirst", "Hello! This is First, called with retry");
        runWithRetry(test, "toRunSecond", "Hello! This is Second, called with retry");
    }


    public void toRunFirst(String s) {
        System.out.println(s);
    }
    public void toRunSecond(String s) {
        System.out.println(s);
    }

    public static Object runWithRetry(Object obj, String methodName, Object... args) throws Exception
    {
        Class<?>[] paramClass = new Class<?>[args.length];
        for(int i=0; i< args.length; i++) {
            paramClass[i] = args[i].getClass();
        }
        Method method = obj.getClass().getDeclaredMethod(methodName, paramClass);

        int retryCount = 2;

        for(int i=0; i< retryCount; i++) {
            try {
                return  method.invoke(obj, args);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
Tremblay answered 9/12, 2019 at 14:14 Comment(0)
P
0

I did not find what I needed so there is mine. The main feature is that it throws the type of Exception you need when maxRetries is reached so you can catch it in the call.

import org.apache.log4j.Logger;

public class TaskUtils {

    public static <E extends Throwable> void retry(int maxRetries, Task<E> task) throws E {
        retry(maxRetries, 0, null, task);
    }

    public static <E extends Throwable> void retry(int maxRetries, long waitTimeMs, Logger logger, Task<E> task) throws E {
        while (maxRetries > 0) {
            maxRetries--;
            try {
                task.run();
            } catch (Exception e) {
                if (maxRetries == 0) {
                    try {
                        throw e;
                    } catch (Exception ignored) { // can't happen but just in case we wrap it in
                        throw new RuntimeException(e);
                    }
                }

                if (logger != null)
                    logger.warn("Attempt " + maxRetries + " failed", e);
                try {
                    Thread.sleep(waitTimeMs);
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

    public interface Task<E extends Throwable> {
        void run() throws E;
    }
}

Usage :

TaskUtils.retry(3, 500, LOGGER, () -> stmClickhouse.execute(
                        "ALTER TABLE `" + database + "`.`" + table.getName() + "` ON CLUSTER " + clusterName + allColumnsSql
                ));
Parade answered 27/7, 2021 at 13:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.