我正在使用Spring's 'HTTP Invoker' remoting solution向许多不同的应用程序公开DAO,但在单个服务器中拥有所有数据库访问权限。
这很好用,但是如果服务器抛出一个HibernateSystemException,Spring会对其进行序列化并通过网络将其发送回客户端。这不起作用,因为客户端没有(也不应该)在其类路径中有HibernateSystemException。
有没有办法让Spring Remoting将我的异常包装在我指定的内容中,这在客户端和服务器之间是常见的,以避免这样的问题?
我知道我可以通过将DAO包含在try / catch中的所有内容包含在我的服务器代码中,但这无疑是草率的。
谢谢, 罗伊
答案 0 :(得分:4)
我也遇到过这个问题;我通过HTTP Invoker公开一个服务,它使用Spring 3.1,JPA 2和Hibernate作为JPA提供者访问数据库。
要解决这个问题,我编写了一个自定义拦截器和一个名为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接线
<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>
答案 1 :(得分:0)
我可以理解你不希望你的客户在他们的类路径中有HibernateSystemException
,但如果你正确使用HTTPInvoker,我认为他们应该这样做。它不是设计成服务外观/接口层的:它所要做的就是让你在远程JVM上运行Java方法,使用HTTP而不是RMI。
因此,如果您真的不希望客户端依赖Hibernate,那么您的try / catch块就是您的选择。 (但我反对这一点,因为它会让调试变得很麻烦:你的堆栈跟踪现在将在客户端和服务器之间划分)。
我自己没有使用它,但您可以尝试使用org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[])
方法添加一个方面来在一个地方捕获该特定异常,而不是在整个地方添加try / catches。
答案 2 :(得分:0)
我认为你DAO前面的Facade图层中的try / catch是完全你想要的,以便完全控制你返回的异常。我同意它最初感觉很难看,但在我看来,它是客户端和DAO之间的重要层面。
您甚至可以返回某种类型的OperationStatus对象,而不是使用void返回类型来传达商店数据API调用的结果(工作,没有)和错误消息。
答案 3 :(得分:0)
我使用类似于N1H4L的解决方案,但 AspectJ 。
首先,我做了所有我希望客户端知道的异常来扩展类 BusinessException (在我的例子中,它是一个非常简单的RuntimeException子类,在jar中带有服务接口和DTO )。
由于我不希望客户对该服务的内部情况有太多了解,我只是说&#34;内部服务器错误&#34;。
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);
}
}
答案 4 :(得分:0)
我使用AspectJ来包装异常,但它不适用于Spring代理中发生的异常,例如:在与数据库的连接失败时注释@Transactional。 但是,RmiServiceExporter上的方法setInterceptor工作正常。