2

我正在使用Spring 的“HTTP Invoker”远程解决方案将 DAO 公开给许多不同的应用程序,但在单个服务器中拥有所有数据库访问权限。

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

是否有一种方法可以让 Spring Remoting 将我的异常包装在我指定的客户端和服务器之间通用的东西中以避免此类问题?

我知道我可以在我的服务器代码中通过将 DAO 所做的一切包装在 try/catch 中来做到这一点,但这无疑是草率的。

谢谢,罗伊

4

5 回答 5

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 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>
于 2012-04-30T18:04:24.253 回答
0

我可以理解您不希望您的客户HibernateSystemException在他们的类路径中拥有,但我认为如果您正确使用 HTTPInvoker,他们应该这样做。它并非设计为服务外观/接口层:它的全部目的是让您在远程 JVM 上运行 Java 方法,使用 HTTP 而不是 RMI。

因此,如果您真的不希望客户端依赖于 Hibernate,那么您的 try/catch 块就是要走的路。(不过我反对这一点,因为它会使调试变得痛苦:您的堆栈跟踪现在将在客户端和服务器之间划分)。

我自己没有使用过它,但是您可以尝试使用该org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[])方法添加一个方面以仅在一个地方捕获该特定异常,而不是在整个地方添加 try/catch。

于 2012-03-12T14:15:06.853 回答
0

我认为在 DAO 前面的 Facade 层中的 try/catch正是您想要的,以便完全控制您返回的异常。我同意它最初感觉很丑,但在我看来,它是客户端和 DAO 之间的重要层。

您甚至可以返回某种 OperationStatus 对象,而不是使用 void 返回类型,来传达结果(工作,没有)和错误消息,用于存储数据 API 调用。

于 2012-03-12T14:30:56.493 回答
0

我使用了类似于 N1H4L 的解决方案,但使用了AspectJ

首先,我做了所有我希望客户端知道的异常来扩展类BusinessException(在我的例子中,它是带有服务接口和 DTO 的 jar 中的 RuntimeException 的一个非常简单的子类)。

由于我不希望客户对服务的内部了解太多,我只说“内部服务器错误”。

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);
    }

}
于 2014-05-12T16:13:36.953 回答
0

我使用 AspectJ 来包装异常,但它不适用于 Spring 代理中发生的异常,例如当与数据库的连接失败时注释 @Transactional。但是,RmiServiceExporter 上的 setInterceptor 方法可以完美运行。

于 2015-07-09T14:21:25.750 回答