Como posso passar uma coleção de exceções como causa raiz?

52

Algum método myMethod,, chama várias execuções paralelas e aguarda suas terminações.

Essas execuções paralelas podem terminar com exceções. Então, myMethodobtém uma lista de exceções.

Desejo passar a lista de exceções como causa raiz, mas a causa raiz pode ser apenas uma única exceção. Claro que posso criar minha própria exceção para alcançar o que quero, mas quero saber se Java, Spring ou Spring Batch tem algo assim fora da caixa.

gstackoverflow
fonte
3
O .NET possui um AggregateExceptionque contém uma lista de exceções. Essa ideia também deve ser aplicada ao Java.
usr
11
Veja também stackoverflow.com/q/8946661/821436
Reinstate Monica - M. Schröder

Respostas:

49

Não tenho certeza se o faria (embora, devido ao JavaDoc, eu não possa lhe dizer por que hesito), mas há a lista de exceções suprimidas Throwable, às quais você pode adicionar via addSuppressed. O JavaDoc não parece dizer que isso é apenas para a JVM usar em try-with-resources:

Anexa a exceção especificada às exceções que foram suprimidas para entregar essa exceção. Esse método é seguro para threads e normalmente é chamado (automaticamente e implicitamente) pela instrução try-with-resources.

O comportamento de supressão é ativado, a menos que seja desativado por meio de um construtor. Quando a supressão está desativada, esse método não faz nada além de validar seu argumento.

Observe que quando uma exceção causa outra exceção, a primeira exceção geralmente é capturada e a segunda exceção é lançada em resposta. Em outras palavras, há uma conexão causal entre as duas exceções. Por outro lado, há situações em que duas exceções independentes podem ser lançadas em blocos de códigos irmãos, em particular no bloco try de uma instrução try-with-resources e no bloco gerado pelo compilador finalmente que fecha o recurso. Nessas situações, apenas uma das exceções lançadas pode ser propagada. Na instrução try-with-resources, quando existem duas exceções, a exceção originada no bloco try é propagada e a exceção do bloco final é adicionada à lista de exceções suprimidas pela exceção do bloco try. Como uma exceção desenrola a pilha,

Uma exceção pode ter suprimido as exceções enquanto também é causada por outra exceção. Se uma exceção tem ou não uma causa é sabido semanticamente no momento de sua criação, ao contrário de uma exceção suprimir ou não outras exceções que normalmente são determinadas apenas após o lançamento de uma exceção.

Observe que o código escrito do programador também pode tirar vantagem de chamar esse método em situações em que há várias exceções entre irmãos e apenas uma pode ser propagada.

Observe o último parágrafo, que parece se adequar ao seu caso.

TJ Crowder
fonte
[...] se uma exceção suprimirá ou não outras exceções [...] normalmente é determinada apenas após o lançamento de uma exceção. Eu imagino que esse não será o caso quando várias exceções suprimidas são coletadas de execuções paralelas.
GOTO 0
24

Exceções e suas causas são sempre apenas uma coisa 1: 1: você pode lançar uma exceção e cada exceção pode ter apenas uma causa (que pode novamente ter uma causa ...).

Isso pode ser considerado uma falha de design, especialmente ao considerar o comportamento multithread como você descreveu.

Essa é uma das razões pelas quais o Java 7 foi adicionado addSuppressedao throwable, que basicamente pode anexar uma quantidade arbitrária de exceções a uma única (a outra motivação principal foi a tentativa com recursos, que precisava de uma maneira de lidar com exceções no bloco final sem cair silenciosamente eles).

Então, basicamente, quando você tem uma exceção que causa falha no processo, você a adiciona como a causa da exceção de nível superior e, se tiver mais alguma, adiciona-as ao original usando addSuppressed. A idéia é que essa primeira exceção "suprima" os outros se tornem membros da "verdadeira cadeia de exceções".

Código de amostra:

Exception exception = null;
for (Foobar foobar : foobars) {
  try {
    foobar.frobnicate();
  } catch (Exception ex) {
    if (exception == null) {
      exception = ex;
    } else {
      exception.addSuppressed(ex);
    }
  }
}
if (exception != null) {
  throw new SomethingWentWrongException(exception);
}
Joachim Sauer
fonte
4
Eu não faria da maneira que você sugere, a menos que uma das exceções subjacentes possa realmente ser destacada como a "principal". Se você escolher arbitrariamente uma das exceções como principal e as outras como suprimidas, estará convidando um chamador a ignorar as exceções suprimidas e apenas a reportar a principal - mesmo que a exceção "principal" seja um TypoInUserInputException e um dos os suprimidos são uma DatabaseCorruptedException.
Ilmari Karonen 22/11/19
11
… Em vez disso, marcaria todas as exceções subjacentes como suprimidas pela SomethingWentWrongException e daria a essa exceção uma mensagem indicando claramente que uma ou mais exceções suprimidas devem seguir, por exemplo, algo como " X X de Y tarefas falharam, consulte a lista de falhas abaixo" "
Ilmari Karonen 22/11/19