如何使用 Java 8 功能简化重试代码块
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38106111/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to simplify retry code block with java 8 features
提问by Moonlit
In my code I have a section which tries to connect to some external interface, if it fails, then it will retry it a fixed number of times. The code works, but is somewhat ugly. I am wondering whether this can be done in a more elegant way using some fancy Java8 features?
在我的代码中,我有一个部分尝试连接到某个外部接口,如果它失败,那么它将重试固定次数。该代码有效,但有点难看。我想知道这是否可以使用一些花哨的 Java8 功能以更优雅的方式完成?
int count = 0;
final int maxRetries = 3;
while ( count < maxRetries )
{
try
{
// Some Code
// break out of loop on success
}
catch ( final ExecutionException e )
{
LOG.debug( "retrying..." );
if ( ++count >= maxRetries )
{
LOG.debug( "do something else...");
//do something else
}
}
}
回答by Brian Goetz
What you can do is separate out the retry logic. You'll need some ancillary scaffolding:
您可以做的是分离重试逻辑。你需要一些辅助脚手架:
interface ThrowingTask {
void run() throws ExecutionException;
}
Now, you write:
现在,你写:
boolean runWithRetries(int maxRetries, ThrowingTask t) {
int count = 0;
while (count < maxRetries) {
try {
t.run();
return true;
}
catch (ExecutionException e) {
if (++count >= maxRetries)
return false;
}
}
}
Now, you can run things with retries without having to conflate your task logic with your retry logic:
现在,您可以通过重试来运行事物,而不必将您的任务逻辑与重试逻辑混为一谈:
runWithRetries(MAX_RETRIES, () -> { /* do stuff */ });
You can tweak this as you like to accept lambdas which are called on retry, return the retry count, etc etc. But the game is to write methods like runWithRetries
which capture the control flow but abstract over what behavior needs to be done -- so you only have to write your retry loop once, and then fill in the actual behavior you want wherever needed.
您可以根据需要进行调整,以接受在重试时调用的 lambda,返回重试计数等。但游戏是编写方法,例如runWithRetries
捕获控制流但抽象需要完成的行为 - 所以你只需编写一次重试循环,然后在需要的地方填写您想要的实际行为。
回答by Jonathan
Using Failsafe:
使用故障安全:
RetryPolicy retryPolicy = new RetryPolicy()
.retryOn(ExecutionException.class)
.withMaxRetries(3);
Failsafe.with(retryPolicy)
.onRetry(r -> LOG.debug("retrying..."))
.withFallback(e -> LOG.debug("do something else..."))
.run(() -> someCode());
It's about as simple and expressive as you can get for your use case.
它与您的用例一样简单和富有表现力。
回答by tkachuko
Well, more functional approach in my opinion will be to use Try
monad which unfortunately is not there for us in jdk 8 :(
好吧,在我看来,更实用的方法是使用Try
monad,不幸的是在 jdk 8 中我们不存在 :(
Nevertheless you still can use better-monads librarywhich provides it. Having that you can come up with some implementation like this:
尽管如此,您仍然可以使用提供它的 Better-monads 库。有了它,你可以想出一些这样的实现:
public static <Out> Try<Out> tryTimes(int times, TrySupplier<Out> attempt) {
Supplier<Try<Out>> tryAttempt = () -> Try.ofFailable(attempt::get);
return IntStream.range(1, times)
.mapToObj(i -> tryAttempt)
.reduce(tryAttempt, (acc, current) -> () -> acc.get().recoverWith(error -> current.get()))
.get();
}
Long story short this function just chains calls of tryAttempt
and in case of failed attempt tries to recoverWith
the next call of tryAttempt
. Client code is going to look like this:
长话短说,这个函数只是链接调用,tryAttempt
如果失败尝试尝试recoverWith
下一次调用tryAttempt
. 客户端代码将如下所示:
tryTimes(10, () -> {
// all the logic to do your possibly failing stuff
}
);
As a result client code is going to get Try<T>
which can be unpacked by direct call of .get()
(in case of success returns the value, in case of failure throws underlying exception) or with other methods described in library documentation.
因此,客户端代码将获得Try<T>
,可以通过直接调用.get()
(如果成功返回值,如果失败则抛出底层异常)或使用库文档中描述的其他方法来解包。
Hope it helps.
希望能帮助到你。
UPDATE:
更新:
This can be done also in functional way using the filter
, findFirst
and limit
and without any external libraries:
这也使用来完成功能性的方式filter
,findFirst
并且limit
,没有任何外部库:
interface ThrowingSupplier<Out> { Out supply() throws Exception; }
public static <Out> Optional<Out> tryTimes(int times, ThrowingSupplier<Out> attempt) {
Supplier<Optional<Out>> catchingSupplier = () -> {
try {
return Optional.ofNullable(attempt.supply());
} catch (Exception e) {
return Optional.empty();
}
};
return Stream.iterate(catchingSupplier, i -> i)
.limit(times)
.map(Supplier::get)
.filter(Optional::isPresent)
.findFirst()
.flatMap(Function.identity());
}
The client code remains the same. Also, please note that it is not going to evaluate expression times
times, but will stop on the first successful attempt.
客户端代码保持不变。另外,请注意它不会计算表达式times
时间,而是会在第一次成功尝试时停止。
回答by v0rin
Similar to some of the suggested approaches, you could create a class that separates the retryfunctionality from the rest of you code. The class below that does just that and also allows you to take an input and return some result:
与一些建议的方法类似,您可以创建一个类,将重试功能与其余代码分开。下面的类就是这样做的,并且还允许您接受输入并返回一些结果:
public class Retrier<T, R> {
private static final int DEFAULT_RETRY_COUNT = 3;
private int retryCount;
private Function<T, R> retriable;
private T input;
public Retrier(T input, Function<T, R> retriable) {
this(input, retriable, DEFAULT_RETRY_COUNT);
}
public Retrier(T input, Function<T, R> retriable, int retryCount) {
this.retryCount = retryCount;
this.retriable = retriable;
this.input = input;
}
public R execute() {
int count = 0;
while(true) {
try {
return retriable.apply(input);
}
catch (Exception e) {
if (++count >= retryCount) {
throw e;
}
}
}
}
}
And then you can use it like this e.g.
然后你可以像这样使用它,例如
String result = new Retrier<String, String>("input", input -> {/* process the input and return the result*/}).execute();
And wherever your code will throw an exception it will be retried.
无论您的代码将在何处抛出异常,它都会被重试。