Спасение проглоченного исключения в 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
Не могли бы вы просто использовать ссылочную переменную для вызова этого метода, если результат равен нулю, тогда вы можете просто отображать сообщение/вызывать исключение, что бы вы ни хотели?
Ответ 7
Если вы используете maven, вы должны исключить пакеты библиотеки.
Исключения зависимостей.
Я надеюсь быть полезным
Ответ 8
Если у вас есть источник класса throwing, вы можете добавить его "в исходный пакет, но в каталог src", используя технику, как указал Бенуат. Затем просто измените
return null;
к
return e;
или
e.printStackTrace();
и др.
Это будет быстрее, чем создание нового Исключения.