在Java中拯救被吞没的异常

在Java中拯救被吞没的异常,java,exception,exception-handling,Java,Exception,Exception Handling,某些第三方库吞没了一个例外: String getAnswer(){ try{ // do stuff, modify instance state, maybe throw some exceptions // ... return computeAnswer(); }catch (SomeException e){ return null; } } //original, unmodifiable 3

某些第三方库吞没了一个例外:

String getAnswer(){
    try{
        // do stuff, modify instance state, maybe throw some exceptions
        // ...
        return computeAnswer(); 
    }catch (SomeException e){
        return null;
    }
}
//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();
}
我很想把它改成:

String getAnswer() throws SomeException{
    // do stuff, modify instance state, maybe throw some exceptions
    // ...
    return computeAnswer();
}
我不能,因为库已经打包到一个罐子里了。那么,有没有办法恢复异常

我不需要重播,带有异常和消息的stacktrace也可以工作

我认为反射在这里没有帮助,
不安全
也许


是的,我知道我可以使用调试器来找出发生了什么,但是如果我需要运行时的异常来记录日志之类的东西,那就没有什么用处了

要根据您的约束来解决这个问题,我会使用方面(比如AspectJ)并将其附加到异常的创建中,即日志记录中(或者让它调用任意的)方法


如果您只想记录stacktrace+异常消息,那么您可以在抛出异常时这样做

请参阅获取堆栈跟踪。只需使用Throwable.getMessage()获取消息并将其写出即可

但是如果您需要代码中的实际异常,您可以尝试将异常添加到ThreadLocal中

为此,您需要这样一个类来存储异常:

package threadLocalExample;

public class ExceptionKeeper
{
    private static ThreadLocal<Exception> threadLocalKeeper = new ThreadLocal<Exception>();

    public static Exception getException()
    {
        return threadLocalKeeper.get();
    }

    public static void setException(Exception e)
    {
        threadLocalKeeper.set(e);
    }

    public static void clearException()
    {
        threadLocalKeeper.set(null);
    }
}
…然后在调用第三方库的整个代码中,它可以设置并使用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();
        }

    }

}

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

这是一个相当肮脏的把戏,它可以比AOP或反编译/重新编译JAR更省力地完成这项工作:

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


如果
SomeException
不是
RuntimeException
和其他第三方代码调用
getAnswer
,则可能会出现问题。在这种情况下,我不确定结果将如何。但您可以通过将
SomeException
包装到自定义
RuntimeException
中来规避此问题。您不能吗只需使用一个引用变量来调用该方法,如果结果为null,则您可以显示一条消息/调用一个异常,无论您需要什么?

代理可以提供帮助。请参阅


我已经为每个抛出的异常调用了
Throwable.printStackTrace()
,但是您可以很容易地将回调更改为调用任何其他Java方法。

您可以在没有反射或AOP的情况下执行此操作。主要思想是抛出另一个(未选中)
SomeException
的构造函数中存在异常。有一些限制(请参见本答案末尾),但我希望它适合您的需要

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

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
    }
}
测试将为绿色,因为原始的
SomeException
将被抛出

限制:

如果出现以下任一情况,此解决方案将不起作用:

  • 如果
    SomeException
    位于
    java.lang
    中,则无法替换
    java.lang
    类(或请参阅)
  • 如果第三方方法有一个
    捕获(Throwable e)
    (这将非常可怕,应该会促使您忽略完整的第三方库)

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


我希望对您有所帮助

如果您有抛出类的源代码,您可以使用@Benoît指出的技术将其添加到“原始包中,但在您的src目录中”。然后只需更改

return null;

等等


这将比创建新异常更快。

调用computeAnswer()而不是调用getAnswer()?如果他准备好使用反射,他确实可以访问该方法。computeAnswer具有私有访问权限(我编写了computeAnswer()来演示,在我的例子中,引发异常的只是一堆混乱)@正如上面提到的JB一样,tom91136:您可以使用反射来“删除”私有访问。如果你觉得这是库中的一个真正的设计问题,请库的开发人员改进它并发布一个更好的版本。或者,如果它是开源的,那么你自己动手吧。这个答案非常棒,教了我一些新技巧。但是第三方库没有调用我的任何代码,它在抛出异常它自己的土地,然后吞没了它!那时你唯一能做的就是内森建议的AOP选项,或者内森没有的反编译选项。我必须维护我自己的一个类的副本,这个类以前出现在一个第三方库中。你可以这样做,在你的构建过程中更新这个jar,而不是一个total fork。这是一个讨厌的黑客行为,但是如果你不想在你的应用程序中引入AOP,这是你最好的选择。哦,好吧,我想AOP是这种情况下唯一实用的解决方案。令人遗憾的是,它甚至不是一个边缘案例,一些遗留库一直在做这种事情。我正在使用的库来自Oracle JDK,特别是用于注释pr的API据我所知,请求者需要SomeException消息和stacktrace:“我不需要重试,带有异常和消息的stacktrace也可以工作”@Benoit是对的,我需要消息和stacktrace,否则我无法记录任何东西。拥有原始异常当然是最好的,但这似乎很难获得。这似乎是一个实用的解决方案,没有
return null;
return e;
e.printStackTrace();