Spring Remoting HTTP invoker - обработка исключений
Я использую решение удаленного взаимодействия "HTTP Invoker" от Spring, чтобы предоставлять DAO множеству различных приложений, но у меня есть доступ ко всем базам данных на одном сервере.
Это работает хорошо, но если сервер генерирует, скажем, HibernateSystemException, Spring сериализует это и отправляет его по сети обратно клиенту. Это не работает, потому что клиент не имеет (и не должен) иметь HibernateSystemException в своем пути к классам.
Может ли быть способ заставить Spring Remoting обернуть мое исключение во что-то, что я укажу, что будет общим для клиента и сервера, чтобы избежать подобных проблем?
Я знаю, что мог бы сделать это в своем серверном коде, обернув все, что DAO делает, в try/catch, но это, по общему признанию, небрежно.
Спасибо Рой
5 ответов
Я столкнулся с этой проблемой также; Я представляю сервис через HTTP Invoker, который обращается к базе данных, используя Spring 3.1, JPA 2 и Hibernate в качестве поставщика JPA.
Чтобы обойти эту проблему, я написал собственный Interceptor и исключение WrappedException. Перехватчик перехватывает исключения, выданные службой, и преобразует исключения и причины в WrappedException, используя отражение и установщики. Предполагая, что клиент имеет WrappedException в своем пути к классам, трассировка стека и исходные имена классов исключений видны клиенту.
Это избавляет клиента от необходимости иметь Spring DAO на своем пути к классам, и, насколько я могу судить, исходная информация трассировки стека не теряется при переводе.
истребитель - перехватчик
public class ServiceExceptionTranslatorInterceptor implements MethodInterceptor, Serializable {
private static final long serialVersionUID = 1L;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Throwable e) {
throw translateException(e);
}
}
static RuntimeException translateException(Throwable e) {
WrappedException serviceException = new WrappedException();
try {
serviceException.setStackTrace(e.getStackTrace());
serviceException.setMessage(e.getClass().getName() +
": " + e.getMessage());
getField(Throwable.class, "detailMessage").set(serviceException,
e.getMessage());
Throwable cause = e.getCause();
if (cause != null) {
getField(Throwable.class, "cause").set(serviceException,
translateException(cause));
}
} catch (IllegalArgumentException e1) {
// Should never happen, ServiceException is an instance of Throwable
} catch (IllegalAccessException e2) {
// Should never happen, we've set the fields to accessible
} catch (NoSuchFieldException e3) {
// Should never happen, we know 'detailMessage' and 'cause' are
// valid fields
}
return serviceException;
}
static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field f = clazz.getDeclaredField(fieldName);
if (!f.isAccessible()) {
f.setAccessible(true);
}
return f;
}
}
исключение
public class WrappedException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String message = null;
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return message;
}
}
Bean Wiring
<bean id="exceptionTranslatorInterceptor" class="com.YOURCOMPANY.interceptor.ServiceExceptionTranslatorInterceptor"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="YOUR_SERVICE" />
<property name="order" value="1" />
<property name="interceptorNames">
<list>
<value>exceptionTranslatorInterceptor</value>
</list>
</property>
</bean>
Я бы сказал, что попробуйте / поймайте в слое Facade перед вашими DAO именно то , что вы хотите, чтобы получить полный контроль над исключениями, которые вы возвращаете. Я согласен, что на первый взгляд это выглядит ужасно, но, по моему мнению, это жизненно важный слой между клиентом и DAO.
Вы могли бы даже возвратить объект OperationStatus некоторого вида, вместо того, чтобы использовать пустые возвращаемые типы, чтобы передать и результат (работал, не сделал) и сообщение об ошибке, для вызовов API хранилища данных.
Я использовал решение, аналогичное N1H4L, но с AspectJ.
Сначала я сделал все исключения, о которых клиент должен знать, чтобы расширить класс BusinessException (который в моем случае является очень простым подклассом RuntimeException в jar с интерфейсом службы и DTO).
Поскольку я не хочу, чтобы клиент много знал о внутренностях службы, я просто говорю "Внутренняя ошибка сервера".
package com.myproduct.myservicepackage;
import com.myproduct.BusinessException;
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class InternalServerErrorExceptionAspect {
@Pointcut("execution(public * com.myproduct.myservicepackage..*Service.*(..))")
public void publicServiceMethod() {}
@Around("publicServiceMethod()")
public Object hideNonBusinessExceptions(ProceedingJoinPoint jp) throws Throwable {
try {
return jp.proceed();
} catch (BusinessException e) {
throw e;
} catch (RuntimeException e) {
e.printStackTrace();
throw new RuntimeException("Internal server error.")
}
}
}
Вот класс BusinessException:
package com.myproduct.BusinessException;
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 8644864737766737258L;
public BusinessException(String msg) {
super(msg);
}
}
Я использовал AspectJ, чтобы обернуть исключение, но оно не работает для исключения, возникающего в Spring-прокси, например, аннотации @Transactional, когда соединение с базой данных не удается. Однако метод setInterceptor в RmiServiceExporter работает отлично.
Я понимаю, что вы не хотите, чтобы ваши клиенты имели HibernateSystemException
в их classpath, но я бы сказал, что они должны, если вы используете HTTPInvoker соответствующим образом. Он не предназначен для того, чтобы быть фасадом службы / уровнем интерфейса: все, что он должен сделать, - это позволить вам запускать методы Java на удаленной JVM, используя HTTP вместо RMI.
Так что если вы действительно не хотите, чтобы клиенты зависели от Hibernate, ваш блок try/catch - это то, что нужно. (Хотя я бы поспорил с этим, поскольку отладка будет затруднена: ваша трассировка стека теперь будет разделена между клиентом и сервером).
Я не использовал это сам, но вы могли бы попробовать org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[])
метод, чтобы добавить аспект, чтобы поймать это конкретное исключение только в одном месте, вместо добавления повтора / ловли повсюду.