Обработка трассировки стека с неизвестными классами исключений
Я реализую сессионный компонент, который бросает ApplicationException
s. Эти исключения имеют цепочечные трассировки стека, которые могут содержать исключения, классы которых недоступны на клиенте. Что-то вроде:
@Override
public void doSomethingSpecial(MyObject o) throws MyException {
try {
legacySystem.handle(o);
} catch (LegacyException e) {
logger.warn(e.getMessage(), e);
throw new MyException(e);
}
}
Здесь возможно, что клиент получит исключение, для которого у него нет класса. Это может привести к:
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at sun.proxy.$Proxy0.doSomethingSpecial(Unknown Source)
at com.myapp.client.Client.main(Client.java:56)
Caused by: java.lang.ClassNotFoundException: MyLegacyException
Я не хочу, чтобы клиент знал все возможные исключения, которые могут быть выданы на стороне сервера, но трассировка стека никогда не бывает плохой.
Как вы справляетесь с этими проблемами? Это сносное решение для реализации Interceptor
что разъединяет трассировку стека, когда исключение отправляется обратно клиенту? Но тогда Interceptor
должен обрабатывать только звонки через RemoteInterface
потому что внутренне меня интересует весь след стека.
3 ответа
Это зависит от вашего типа клиента. Если клиент - это другая команда, которая разрабатывает другой компонент или подсистему, я согласен с вами по поводу:
Трассировка стека никогда не бывает плохой
Но если они являются клиентами, которые не имеют представления о внутренних компонентах вашего приложения, то у них нет оснований знать ваши классы исключений или даже видеть следы вашего стека. Было бы неплохо иметь протокол, который заставляет вас перехватывать все исключения и помещать их в класс исключений высокого уровня с error_code
имущество. Таким образом, вы можете иметь конкретный код ошибки для каждого оператора catch в вашем приложении, и вы предоставите своим клиентам список этих кодов.
В любом случае, с технической точки зрения, если ваши клиенты не имеют доступа к вашему внутреннему Exception
классы, поэтому они не могут иметь доступ к вашей трассировке стека без ClassNotFoundException
, Если вы действительно хотите, чтобы они видели трассировку стека, одним из решений может быть использование Aspect, который находится на самом верхнем уровне вашего API (который будет вызываться клиентами) и перехватывает все исключения, записывает их трассировки стека. в String
и отправляет это как свойство окончательного исключения, которое будет перехвачено вызывающей стороной. Таким образом, вызывающая сторона может получить доступ к трассировке стека как отформатированное свойство String исключения.
Редактировать:
Вы даже можете настроить свой скрипт сборки, чтобы этот Аспект никогда не был частью ваших версий выпуска. Таким образом, вы можете выдавать эти сообщения трассировки стека только в вашей отладочной версии.
Поскольку RMI основывается на Serialization
ты можешь использовать Serialization
Особенности условно заменить исключения.
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
public class CarryException extends RuntimeException implements Serializable
{
final String exceptionClass;
public CarryException(Exception cause)
{
super(cause.getMessage());
exceptionClass=cause.getClass().getName();
setStackTrace(cause.getStackTrace());
}
@Override
public String getMessage()
{
// if we get here, reconstructing the original exception did not work
return exceptionClass+": "+super.getMessage();
}
/** Invoked by Serialization to get the real instance */
final Object readResolve() throws ObjectStreamException
{
try
{
Exception ex = Class.forName(exceptionClass).asSubclass(Exception.class)
.getConstructor(String.class).newInstance(super.getMessage());
ex.setStackTrace(getStackTrace());
return ex;
}
catch(InstantiationException|IllegalAccessException|ClassNotFoundException
| IllegalArgumentException|InvocationTargetException|NoSuchMethodException
| SecurityException ex)
{
// can't reconstruct exception on client side
}
return this; // use myself as substitute
}
}
Теперь вы можете выдать любое исключение клиенту путем throw new CarryException(originalException);
, CarryException
всегда записывает трассировку стека и сообщение исходного исключения и воссоздает исходное исключение на стороне клиента, если класс доступен. В противном случае клиент видит CarryException
так очевидно, что один тип исключения должен быть известен на стороне клиента.
Тип исключения должен иметь стандартный конструктор, принимающий сообщение String
на реконструкцию на работу. (Все остальное было бы слишком сложно). Но большинство типов исключений имеют это.
Есть еще одна загвоздка: замена через Serialization
работает только если Serialization
участвует, поэтому вы не должны вызывать методы класса реализации напрямую, когда находитесь внутри той же JVM. В противном случае вы видите CarryException
безусловно. Таким образом, вы должны использовать заглушку даже локально, например
((MyRemoteInterface)RemoteObject.toStub(myImplementation)).doSomethingSpecial();
Обновить
Если MyException
известен клиенту и только LegacyException
Разумеется, нет следующих работ:
catch (LegacyException e) {
logger.warn(e.getMessage(), e);
MyException me=new MyException(e.toString());
me.setStackTrace(e.getStackTrace());
throw me;
}
Я думал о небольшом обходном решении, но это всего лишь непроверенные предположения.
Вы инициализируете свое внешнее исключение внутренним исключением. Но если мы посмотрим на javadoc Throwable, мы увидим методы get и setStackTrace (StackTraceElement [] stackTrace)
StackTraceElement инициализируется со строками. Поэтому, возможно, вы можете получить трассировку стека из внутреннего исключения и установить его во внешнее исключение (MyException).