Rescuing a swallowed Exception in Java

Some 3rd party library swallowed an Exception:

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

As much as I want to change it into:

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

I can't, because the library is already packaged into a jar. So, is there a way to bring the exception back?

I don't need to rethrow, a stacktrace with exception and message would work too.

I don't think reflection would help here, Unsafe perhaps?

Yes I know I can use a debugger to find out what's happening, but that wouldn't be very useful if I need the exception at runtime for logging and stuff like that


You can do it without reflection or AOP. The main idea is to throw another (unchecked) exception in the constructor of SomeException . There are some limitations (see at the end of this answer) but I hope it fits your needs.

You need to replace the SomeException with a new version (just create a SomeException.java file in the original package but in your src directory) with something like :

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

The SomeExceptionWrapperException has to be unchecked (inherit from RuntimeException or Error). It will be our wrapper to carry the SomeException accross the ugly 3rd party catch(...)

Then you can catch the SomeExceptionWrapperException in your code (and eventually rethrow the original 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();
}

The test will be green as the original SomeException , will be thrown.

Limitations:

This solution will not work if either :

  • if SomeException is in java.lang as you cannot replace java.lang classes (or see Replacing java class?)
  • if the 3rd party method has a catch(Throwable e) (which will be horrible and should motivate you to ignore the full 3rd party library)

  • To solve this based on your constraints I would use aspects (something like AspectJ) and attach it to the creation of your exception, logging (or having it call some arbitrary) method then.

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


    If all you're looking for is to log the stacktrace + exception message, you could do that at the point you're throwing your exception.

    See Get current stack trace in Java to get the stack trace. You can simply use Throwable.getMessage() to get the message and write it out.

    But if you need the actual Exception within your code, you could try and add the exception into a ThreadLocal.

    To do this, you would need a class like this that can store the exception:

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

    ... then in your code which throws the Exception, the code that the 3rd party library calls, you can do something like this to record the exception before you throw it:

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

    ... then in your overall code, the one that calls the third party library, it can setup and use the ThreadLocal class like this:

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

    Beware of ThreadLocals. They have their use, but they are a great way of creating memory leaks. So if your application has a lot of threads that would execute this code, be sure to look at the memory footprint and make sure the ThreadLocals aren't taking up too much memory. Being sure to clear out the ThreadLocal's data when you know you no longer need it should prevent that.

    链接地址: http://www.djcxy.com/p/82420.html

    上一篇: 在Java应用程序中查找线程创建源

    下一篇: 在Java中拯救吞噬异常