Resgatando uma exceção engolida em Java

Uma biblioteca de terceiros engoliu uma exceção:

String getAnswer(){ try{ // do stuff, modify instance state, maybe throw some exceptions // ... return computeAnswer(); }catch (SomeException e){ return null; } } 

Tanto quanto eu quero mudar isso em:

 String getAnswer() throws SomeException{ // do stuff, modify instance state, maybe throw some exceptions // ... return computeAnswer(); } 

Eu não posso, porque a biblioteca já está empacotada em um jarro. Então, existe uma maneira de trazer a exceção de volta?

Eu não preciso retroceder, um stacktrace com exceção e mensagem funcionaria também.

Eu não acho que a reflection ajudaria aqui, Unsafe talvez?

Sim, eu sei que posso usar um depurador para descobrir o que está acontecendo, mas isso não seria muito útil se eu precisasse da exceção em tempo de execução para registrar e coisas assim

Você pode fazer isso sem reflection ou AOP. A idéia principal é lançar outra exceção (não verificada) no construtor de SomeException . Existem algumas limitações (veja no final desta resposta), mas espero que seja adequado às suas necessidades.

Você precisa replace o SomeException por uma nova versão (basta criar um arquivo SomeException.java no pacote original, mas no diretório src) com algo como:

 package com.3rdpartylibrary; public class SomeException extends Exception { public static class SomeExceptionWrapperException extends RuntimeException { public SomeExceptionWrapperException(final SomeException ex) { super(ex.getMessage(), ex); } } public SomeException(final String message) { super(message); throw new SomeExceptionWrapperException(this); //<=== the key is here } } 

O SomeExceptionWrapperException tem que estar desmarcado (herda de RuntimeException ou Error). Será o nosso invólucro para levar o SomeException através do feio 3o partido catch(...)

Então você pode pegar o SomeExceptionWrapperException em seu código (e, eventualmente, recolocar o SomeException original:

 //original, unmodifiable 3rdParty code, here as a example public String getAnswer() { try { //some code throw new SomeException("a message"); } catch (final SomeException e) { return null; } } //a wrapper to getAnswer to unwrapp the `SomeException` public String getAnswerWrapped() throws SomeException { try { return getAnswer(); } catch (final SomeExceptionWrapperException e) { throw (SomeException) e.getCause(); } } @Test(expected = SomeException.class) public void testThrow() throws SomeException { final String t = getAnswerWrapped(); } 

O teste será verde como o SomeException original, será lançado.

Limitações:

Esta solução não funcionará se:

  • if SomeException está em java.lang porque você não pode replace classs java.lang (ou veja Substituindo a class java? )
  • se o método de terceiros tiver um problema catch(Throwable e) (que será horrível e deve motivá-lo a ignorar a biblioteca completa de terceiros)

Para resolver isso com base em suas restrições, eu usaria aspectos (algo como AspectJ) e appendia à criação de sua exceção, registrando (ou fazendo com que ela chamasse algum método arbitrário).

http://www.ibm.com/developerworks/library/j-aspectj/

Se tudo o que você está procurando é registrar a mensagem de exceção + stacktrace, você pode fazer isso no ponto em que está lançando sua exceção.

Consulte Obter rastreamento de pilha atual em Java para obter o rastreamento de pilha. Você pode simplesmente usar Throwable.getMessage () para pegar a mensagem e escrevê-la.

Mas se você precisar da Exceção real em seu código, poderá tentar adicionar a exceção a um ThreadLocal.

Para fazer isso, você precisaria de uma class como essa para armazenar a exceção:

 package threadLocalExample; public class ExceptionKeeper { private static ThreadLocal threadLocalKeeper = new ThreadLocal(); public static Exception getException() { return threadLocalKeeper.get(); } public static void setException(Exception e) { threadLocalKeeper.set(e); } public static void clearException() { threadLocalKeeper.set(null); } } 

… então no seu código que lança a Exceção, o código que a biblioteca de terceiros chama, você pode fazer algo assim para gravar a exceção antes de lançá-la:

 package threadLocalExample; public class ExceptionThrower { public ExceptionThrower() { super(); } public void doSomethingInYourCode() throws SomeException { boolean someBadThing = true; if (someBadThing) { // this is bad, need to throw an exception! SomeException e = new SomeException("Message Text"); // but first, store it in a ThreadLocal because that 3rd party // library I use eats it ExceptionKeeper.setException(e); // Throw the exception anyway - hopefully the library will be fixed throw e; } } } 

… então, em seu código geral, aquele que chama a biblioteca de terceiros, ele pode configurar e usar a class ThreadLocal assim:

 package threadLocalExample; import thirdpartylibrary.ExceptionEater; public class MainPartOfTheProgram { public static void main(String[] args) { // call the 3rd party library function that eats exceptions // but first, prepare the exception keeper - clear out any data it may have // (may not need to, but good measure) ExceptionKeeper.clearException(); try { // now call the exception eater. It will eat the exception, but the ExceptionKeeper // will have it ExceptionEater exEater = new ExceptionEater(); exEater.callSomeThirdPartyLibraryFunction(); // check the ExceptionKeeper for the exception Exception ex = ExceptionKeeper.getException(); if (ex != null) { System.out.println("Aha! The library ate my exception, but I found it"); } } finally { // Wipe out any data in the ExceptionKeeper. ThreadLocals are real good // ways of creating memory leaks, and you would want to start from scratch // next time anyway. ExceptionKeeper.clearException(); } } } 

Cuidado com os ThreadLocals. Eles têm seu uso, mas são uma ótima maneira de criar vazamentos de memory. Portanto, se seu aplicativo tiver muitos threads que executariam esse código, certifique-se de examinar o espaço ocupado pela memory e certificar-se de que os ThreadLocals não estão ocupando muita memory. Certificar-se de limpar os dados do ThreadLocal quando você sabe que não precisa mais dele deve evitar isso.

O agente da JVMTI pode ajudar. Veja a questão relacionada .

Eu fiz um agente que chama Throwable.printStackTrace() para cada exceção lançada, mas você pode facilmente alterar o retorno de chamada para invocar qualquer outro método Java.

Um truque bastante sujo que poderia fazer o trabalho com menos esforço que o AOP ou de- / recompilar o JAR:

Se você puder copiar o código-fonte, poderá criar uma versão com patch da class em questão com sua versão do método getAnswer . Em seguida, coloque-o em seu caminho de class antes da biblioteca de terceiros que contém a versão indesejada de getAnswer .

Podem surgir problemas se SomeException não for um RuntimeException e outro código de terceiros chamar getAnswer . Nessa situação, não tenho certeza de como o comportamento resultante será. Mas você poderia contornar isso envolvendo o SomeException em uma RuntimeException personalizada.

Você poderia não apenas usar uma variável de referência para chamar esse método, se o resultado for um nulo, então você pode apenas exibir uma mensagem / chamar uma exceção, o que você quiser?

Se você estiver usando maven, você excluirá pacotes da biblioteca.

Exclusões de dependência .

Espero ser útil

Se você tiver a fonte para a class de lançamento , você pode adicioná-la “no pacote original, mas no seu diretório src” usando a técnica como @ Benoît apontou. Então apenas mude

 return null; 

para

 return e; 

ou

 e.printStackTrace(); 

etc.

Isso seria mais rápido do que fazer uma nova Exceção.