Spring Remoting HTTP调用程序 - 异常处理

时间:2012-03-12 13:58:37

标签: spring spring-remoting

我正在使用Spring's 'HTTP Invoker' remoting solution向许多不同的应用程序公开DAO,但在单个服务器中拥有所有数据库访问权限。

这很好用,但是如果服务器抛出一个HibernateSystemException,Spring会对其进行序列化并通过网络将其发送回客户端。这不起作用,因为客户端没有(也不应该)在其类路径中有HibernateSystemException。

有没有办法让Spring Remoting将我的异常包装在我指定的内容中,这在客户端和服务器之间是常见的,以避免这样的问题?

我知道我可以通过将DAO包含在try / catch中的所有内容包含在我的服务器代码中,但这无疑是草率的。

谢谢, 罗伊

5 个答案:

答案 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工作正常。