Skip to content

Instantly share code, notes, and snippets.

@komiya-atsushi
Last active December 19, 2015 12:39
Show Gist options
  • Save komiya-atsushi/5956785 to your computer and use it in GitHub Desktop.
Save komiya-atsushi/5956785 to your computer and use it in GitHub Desktop.
https://github.com/robertsosinski/retryable これの Java 版的なもの。再試行処理を記述を楽にしてくれます。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 処理の再試行をしやすくする機能を提供します。
*
* @author KOMIYA Atsushi
*/
public class Retryable {
/**
* 戻り値のない処理を定義するインタフェースです。
*
* @author KOMIYA Atsushi
*/
public static interface VoidFunction {
void call() throws Exception;
}
/**
* 戻り値のある処理を定義するインタフェースです。
*
* @author KOMIYA Atsushi
*/
public static interface Function<T> {
T call() throws Exception;
}
/**
* 再試行に失敗した場合にこの例外がスローされます。
*
* @author KOMIYA Atsushi
*/
public static class CannotRetryException extends Exception {
CannotRetryException(String message) {
super(message);
}
CannotRetryException(String message, Exception caused) {
super(message, caused);
}
}
/** 再試行の上限回数を表します */
private int tries = 1;
/** この例外がキャッチされたときに再試行をします */
private List<Class<? extends Exception>> retryingExceptions = Collections.emptyList();
/** 再試行をする際に待機する時間 (ミリ秒) を表します */
private long intervalMillis;
/** 再試行によって抑制された例外を保持します */
private List<Exception> suppressedExceptions = new ArrayList<>();
/**
* 再試行の上限回数を指定します。
* <p>
* 既定の再試行回数は 1 回です。
* </p>
*
* @param tries 1 以上の再試行回数を指定します。
* @return このオブジェクト自身を返却します。
*/
public Retryable tries(int tries) {
if (tries <= 0) {
throw new IllegalArgumentException("tries には 1 以上の値を指定する必要があります。");
}
this.tries = tries;
return this;
}
/**
* 再試行対象の例外を、その Class オブジェクトを列挙して指定します。
* <p>
* このメソッドを呼ぶことなく {@link #doTry(albert.util.Retryable.Function)} メソッドなどを呼び出した場合、
* {@link Exception} クラスもしくはサブクラスの例外が発生した際にその内容に関わらず、すべて再試行処理します。
* </p>
*
* @param retryingExceptions 例外発生時に再試行させたい例外の Class オブジェクトを列挙します
* @return このオブジェクト自身を返却します。
*/
public Retryable when(Class<? extends Exception>... retryingExceptions) {
if (retryingExceptions == null) {
throw new IllegalArgumentException("retryingExceptions には null を指定することはできません。");
}
this.retryingExceptions = Arrays.asList(retryingExceptions);
return this;
}
/**
* 再試行をする際に待機する時間をミリ秒単位で指定します。
*
* @param intervalMillis 待機をする時間のミリ秒表現。0 以上の値を指定します。
* @return このオブジェクト自身を返却します。
*/
public Retryable intervalMillis(long intervalMillis) {
if (intervalMillis < 0) {
throw new IllegalArgumentException("intervalMillis には 0 以上の値を指定する必要があります。");
}
this.intervalMillis = intervalMillis;
return this;
}
/**
* 戻り値のない処理を実行します。
*
* @param function
* @throws CannotRetryException
*/
public void doTry(VoidFunction function) throws CannotRetryException {
int retryCount = 0;
while (true) {
preProcess(retryCount);
try {
function.call();
return;
} catch (Exception e) {
processCaughtException(e);
}
retryCount++;
}
}
/**
* 戻り値のある処理を実行します。
*
* @param function
* @param <T>
* @return
* @throws CannotRetryException
*/
public <T> T doTry(Function<T> function) throws CannotRetryException {
int retryCount = 0;
while (true) {
preProcess(retryCount);
try {
T result = function.call();
return result;
} catch (Exception e) {
processCaughtException(e);
}
retryCount++;
}
}
/**
* 処理中に発生した例外がある場合に、その例外を返却します。
*
* @return
*/
public List<Exception> getSuppressedExceptions() {
return Collections.unmodifiableList(suppressedExceptions);
}
private void preProcess(int retryCount) throws CannotRetryException {
if (retryCount > tries) {
throw createCannotRetryException(String.format("再試行回数の上限 %d 回を超えました。", tries), null);
}
if (retryCount > 0) {
if (intervalMillis > 0) {
try {
Thread.sleep(intervalMillis);
} catch (InterruptedException e) {
throw new RuntimeException("予期しない例外が発生しました。", e);
}
}
}
}
private void processCaughtException(Exception e) throws CannotRetryException {
if (retryingExceptions.isEmpty()) {
suppressedExceptions.add(e);
return;
}
Class<? extends Exception> caughtExceptionClass = e.getClass();
for (Class<? extends Exception> exceptionClass : retryingExceptions) {
if (exceptionClass.isAssignableFrom(caughtExceptionClass)) {
suppressedExceptions.add(e);
return;
}
}
throw createCannotRetryException("再試行対象外の例外が発生しました。", e);
}
private CannotRetryException createCannotRetryException(String message, Exception caused) {
CannotRetryException result;
if (caused != null) {
result = new CannotRetryException(message, caused);
} else {
result = new CannotRetryException(message);
}
for (Exception suppressed : suppressedExceptions) {
result.addSuppressed(suppressed);
}
return result;
}
}
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.IOException;
/**
* Retryable デモ。
*
* @author KOMIYA Atsushi
*/
public class RetryableDemo {
public static void main(String[] args) throws Retryable.CannotRetryException {
String url = "http://example.iana.org/";
final HttpGet get = new HttpGet(url);
final HttpClient client = new DefaultHttpClient();
HttpResponse resp = new Retryable()
.when(IOException.class)
.intervalMillis(1000)
.tries(3)
.doTry(new Retryable.Function<HttpResponse>() {
@Override
public HttpResponse call() throws Exception {
return client.execute(get);
}
});
System.out.println(resp.getStatusLine().toString());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment