在Java中拯救吞噬的exception

一些第三方图书馆吞下了一个例外:

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

我想把它改成:

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

我不能,因为库已经打包成一个jar子。 那么,有没有办法将exception带回来?

我不需要重新抛出,带有exception的堆栈跟踪和消息也可以工作。

我认为反思在这里没有帮助,也许Unsafe

是的我知道我可以使用调试器来查明发生了什么,但如果我在运行时需要exception以进行日志记录和那样的东西,这将不会非常有用

你可以不用reflection或AOP来做。 主要思想是在SomeException的构造函数中抛出另一个(未经检查的)exception。 有一些限制(请参阅本答复的最后部分),但我希望它符合您的需求。

您需要将SomeException替换为新版本(只需在原始包中创建SomeException.java文件,但在src目录中),例如:

 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 } } 

必须取消选中SomeExceptionWrapperException (从RuntimeException或Errorinheritance)。 我们的包装器会在丑陋的第三方catch(...)携带SomeException catch(...)

然后你可以在代码中捕获SomeExceptionWrapperException (并最终重新抛出原始的SomeException:

 //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(); } 

测试将绿色作为原始的SomeException ,将被抛出。

限制:

如果有以下任何一种情况,此解决方

  • 如果SomeExceptionjava.lang因为你无法替换java.lang类(或者参见替换java类? )
  • 如果第三方方法有一个catch(Throwable e) (这将是可怕的,应该激励你忽略完整的第三方库)

为了根据你的约束来解决这个问题,我会使用方面(类似于AspectJ)并将它附加到exception的创建,然后记录(或者让它调用一些任意的)方法。

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

如果您要查找的只是记录stacktrace +exception消息,那么您可以在抛出exception时执行此操作。

请参阅使用Java获取当前堆栈跟踪以获取堆栈跟踪。 您可以简单地使用Throwable.getMessage()来获取消息并将其写出来。

但是,如果您需要在代码中使用实际的Exception,则可以尝试将该exception添加到ThreadLocal中。

要做到这一点,你需要一个可以存储exception的类:

 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); } } 

…然后在你的代码抛出exception,第三方库调用的代码,你可以做这样的事情来记录exception,然后再抛出它:

 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; } } } 

…然后在您的整个代码中,调用第三方库的代码,它可以设置和使用ThreadLocal类,如下所示:

 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(); } } } 

小心ThreadLocals。 它们有它们的用途,但它们是创建内存泄漏的好方法。 因此,如果您的应用程序有很multithreading可以执行此代码,请务必查看内存占用并确保ThreadLocals不占用太多内存。 当你知道不再需要它时,一定要清除ThreadLocal的数据应该可以防止这种情况发生。

JVMTI代理可以提供帮助。 查看相关问题 。

我已经为每个抛出的exception创建了一个调用Throwable.printStackTrace()的代理 ,但是您可以轻松地更改回调以调用任何其他Java方法。

一个相当肮脏的技巧,可以比AOP更少的工作或解除/重新编译JAR来完成工作:

如果您可以复制源代码,则可以使用您的getAnswer方法版本创建相关类的修补版本。 然后将它放在包含不需要的getAnswer版本的第三方库之前的类路径中。

如果SomeException不是RuntimeException而其他第三方代码调用getAnswer则可能会出现问题。 在这种情况下,我不确定最终的行为将如何。 但是你可以通过在自定义RuntimeException包装SomeException来绕过这个。

你能不能只使用引用变量来调用该方法,如果结果是null,那么你可以只显示一条消息/调用exception,无论你想要什么?

如果您使用的是maven,则会排除该库的包。

依赖性排除 。

我希望能有所帮助

如果你有投掷类的源代码,你可以使用@Benoît指出的技术“在原始包中但在你的src目录中”添加它。 然后改变

 return null; 

 return e; 

要么

 e.printStackTrace(); 

等等

这比制作新的例外更快。