Спасение проглоченного исключения в Java

Некоторые сторонние библиотеки проглотили исключение:

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

Я не могу, потому что библиотека уже упакована в банку. Итак, есть ли способ вернуть исключение?

Мне не нужно реконструировать, будет работать stacktrace с исключением и сообщение.

Я не думаю, что размышление поможет здесь, Unsafe возможно?

Да, я знаю, что я могу использовать отладчик, чтобы узнать, что происходит, но это не очень полезно, если мне нужно исключение во время выполнения для ведения журнала и т.д.

Ответы

Ответ 1

Вы можете сделать это без отражения или AOP. Основная идея состоит в том, чтобы выбросить другое (непроверенное) исключение в конструкторе SomeException. Есть некоторые ограничения (см. В конце этого ответа), но я надеюсь, что это соответствует вашим потребностям.

Вам нужно заменить 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 или Error). Это будет наша обертка, чтобы нести 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, будет выброшен.

Ограничения:

Это решение не будет работать, если:

  • Если SomeException находится в java.lang, поскольку вы не можете заменить классы java.lang (или см. Замена класса java?)
  • если сторонний метод имеет catch(Throwable e) (что будет ужасно и должно мотивировать вас игнорировать полную стороннюю библиотеку)

Ответ 2

Чтобы решить эту проблему на основе ваших ограничений, я бы использовал аспекты (что-то вроде AspectJ) и приложил их к созданию вашего исключения, тогда журнал (или его вызов вызывает какой-то произвольный) метод.

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

Ответ 3

Если все, что вам нужно, это зарегистрировать сообщение stacktrace + exception, вы можете сделать это в момент, когда вы выбрали исключение.

См. Получить текущую трассировку стека в Java, чтобы получить трассировку стека. Вы можете просто использовать 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);
    }
}

... тогда в вашем коде, который выдает исключение, код, который вызывает сторонняя библиотека, вы можете сделать что-то подобное, чтобы записать исключение, прежде чем вы его выбросите:

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. У них есть свое применение, но они являются отличным способом создания утечек памяти. Поэтому, если ваше приложение имеет много потоков, которые будут выполнять этот код, обязательно посмотрите на область памяти и убедитесь, что ThreadLocals не занимают слишком много памяти. Убедившись очистить данные ThreadLocal, когда вы знаете, что вам больше не нужно, это должно помешать этому.

Ответ 4

JVMTI может помочь агент. См. Вопрос .

Я сделал agent, который вызывает Throwable.printStackTrace() для каждого вызванного исключения, но вы можете легко изменить обратный вызов для вызова любой другой метод Java.

Ответ 5

Довольно грязный трюк, который мог бы выполнять работу с меньшими усилиями, чем AOP, или декомпозировать JAR:

Если вы можете скопировать исходный код, вы можете создать исправленную версию рассматриваемого класса с вашей версией метода getAnswer. Затем поместите его в свой путь класса перед третьей стороной, в которой содержится нежелательная версия getAnswer.

Проблемы могут возникнуть, если SomeException не является RuntimeException и другими сторонними кодовыми вызовами getAnswer. В этой ситуации я не уверен, каким будет результат. Но вы можете обойти это, обернув SomeException в пользовательский RuntimeException.

Ответ 6

Не могли бы вы просто использовать ссылочную переменную для вызова этого метода, если результат равен нулю, тогда вы можете просто отображать сообщение/вызывать исключение, что бы вы ни хотели?

Ответ 8

Если у вас есть источник класса throwing, вы можете добавить его "в исходный пакет, но в каталог src", используя технику, как указал Бенуат. Затем просто измените

return null;

к

return e;

или

e.printStackTrace();

и др.

Это будет быстрее, чем создание нового Исключения.