#java #spring #asynchronous #concurrency #completable-future
Вопрос:
Как следует из названия, у меня есть некоторые странные требования, и это то, как обрабатывать исключения в вышеупомянутом случае.
Поэтому мне в основном нужно выполнить вложенную проверку. Некоторые из этих проверок независимы от других, некоторые-нет. Если возникает (не)проверенное исключение, оно должно быть передано самому верхнему вызывающему. Под распространяемым и самым главным вызывающим, я имею в виду уровень контроллера, так ControllerAdvice
что можете включиться.
Вот какой-то код:
- У нас есть базовый контроллер, который является нашим вызывающим устройством асинхронных функций «верхнего» уровня.
@RestController @Сопоставление запросов(«/тест») @AllArgsConstructor контроллер открытого класса {
private final Helper helper; public String test(String code, String system) throws ExecutionException, InterruptedException { List<CompletableFuture<Boolean>> futureValidations = List.of(helper.codeableConceptValidator(code, system)); CompletableFuture<Void> allFutureValidations = CompletableFuture.allOf( futureValidations.toArray(new CompletableFuture[futureValidations.size()])); CompletableFuture<List<Boolean>> validationResult = allFutureValidations.thenApply(v -> { return futureValidations.stream() .map(CompletableFuture::join) //futureValidation -> futureValidation.join() .collect(Collectors.toList()); }); CompletableFuture<Long> countFuture = validationResult.thenApply(allValidations -> { return allValidations.stream() .filter(Boolean::valueOf) .count(); }); if (countFuture.get() == futureValidations.size()){ System.out.println("ok"); return "ok"; } return "not ok"; } }
Часть countFuture.get()
— это создание непроверенных исключений , и они могут быть обработаны ControllerAdvice
, пока все хорошо.
Теперь у нас есть helper.codeableConceptValidator(code, system)
, которые также возвращают CompletedFuture<Boolean>
использование supplyAsync
, но когда мы это делаем get()
, когда нужно как-то обработать InterruptedException
, ExecutionException
и пользовательское проверенное исключение из другой библиотеки. Это последнее исключение очень важно, так как эта библиотека также использует RestControllerAdvice
with ExceptionHandler
для возврата пользовательского POJO вызывающему. Мне нужно, чтобы это исключение было выдано с моего контроллера.
@Component
@AllArgsConstructor
public class Helper {
private final Executor executor;
private final StringValidator stringValidator;
private final TerminologyService terminologyService;
public CompletableFuture<Boolean> codeableConceptValidator(String code, String system) {
return CompletableFuture.supplyAsync(() -> {
List<CompletableFuture<Boolean>> futureValidations =
List.of(stringValidator.codeValidator(code),
stringValidator.systemValidator(system)
);
CompletableFuture<Void> allFutureValidations = CompletableFuture.allOf(
futureValidations.toArray(new CompletableFuture[futureValidations.size()]));
CompletableFuture<List<Boolean>> validationResult = allFutureValidations.thenApply(v -> {
return futureValidations.stream()
.map(CompletableFuture::join) //futureValidation -> futureValidation.join()
.collect(Collectors.toList());
});
CompletableFuture<Long> countFuture = validationResult.thenApply(allValidations -> {
return allValidations.stream()
.filter(Boolean::valueOf)
.count();
});
try {
if (countFuture.get() == futureValidations.size()){
try{
terminologyService.validateCode(system, code);
//com.base.package.otherproject.exceptions.CustomException;
}catch (ByCtsApiException e) {
e.printStackTrace();
}
return false;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return false;
}, executor);
}
}
И, наконец, вот простое StringValidator
.
@Component
@AllArgsConstructor
public class StringValidator {
private final Executor executor;
public CompletableFuture<Boolean> codeValidator(String code){
return CompletableFuture.supplyAsync(code::isEmpty, executor);
}
public CompletableFuture<Boolean> systemValidator(String system){
return CompletableFuture.supplyAsync(system::isEmpty, executor);
}
}
Ответ №1:
Вместо get
этого вы можете вызвать join
то , что не требует обработки проверенных исключений. Но вам следует переосмыслить дизайн вызова методов блокировки в переданной функции supplyAsync
. Весь смысл этого API заключается в том, что вы можете связывать зависимые действия для создания нового CompletableFuture
(или CompletionStage
когда вы работаете только с интерфейсом), не дожидаясь завершения.
Когда вы хотите объединить результат двух фьючерсов, вы можете использовать thenCombine
, например , чтобы получить будущее, которое только true
тогда, когда оба фьючерса оцениваются true
, вы можете использовать
CompletableFuture<Boolean> a = stringValidator.codeValidator(code);
CompletableFuture<Boolean> b = stringValidator.systemValidator(system);
CompletableFuture<Boolean> combined = a.thenCombine(b, Boolean::logicalAnd);
или короткие
CompletableFuture<Boolean> combined = stringValidator.codeValidator(code)
.thenCombine(stringValidator.systemValidator(system), Boolean::logicalAnd);
Если вы хотите обработать динамическое количество фьючерсов, приведенное в виде списка, вы можете использовать
List<CompletableFuture<Boolean>> futureValidations =
List.of(stringValidator.codeValidator(code), stringValidator.systemValidator(system));
CompletableFuture<Boolean> combined = futureValidations.stream()
.reduce((a, b) -> a.thenCombine(b, Boolean::logicalAnd))
.orElseGet(() -> CompletableFuture.completedFuture(true));
Это работает разумно, если список не слишком велик. Для больших списков, скажем, значительно более двадцати фьючерсов, код может пострадать от того факта, что типичные операции последовательного сокращения не создают сбалансированное дерево зависимых фьючерсов.
В этом случае вы можете прибегнуть к allOf
:
CompletableFuture<Boolean> combined =
CompletableFuture.allOf(futureValidations.toArray(new CompletableFuture<?>[0]))
.thenApply(x -> futureValidations.stream().allMatch(CompletableFuture::join));
В этом коде join
можно безопасно вызывать, так как последующая операция будет выполнена только после завершения всех фьючерсов. На самом деле, мы можем даже предположить, что join
здесь никогда не возникает исключения, так как будущее, возвращаемое по allOf
, будет завершено исключительно в том случае, если какое-либо из входных фьючерсов будет завершено исключительно, и поэтому будет следующим thenApply
этапом без какой-либо оценки функции. Поэтому мы можем использовать короткое замыкание allMatch
; исключения были обработаны превентивно, поэтому, когда мы вводим код, никаких исключений не произошло, и результат будет false
, как только мы столкнемся с первым false
.