【问题标题】:Spring Remoting HTTP invoker - exception handlingSpring Remoting HTTP 调用程序 - 异常处理
【发布时间】:2012-03-12 13:58:36
【问题描述】:

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

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

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

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

谢谢, 罗伊

【问题讨论】:

    标签: spring spring-remoting


    【解决方案1】:

    我也遇到过这个问题;我正在通过 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>
    

    【讨论】:

    • 完美,这就是我想要的。在解决这个问题一段时间后,我认为我需要一个拦截器来处理它,但从未编码。谢谢!
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        我使用了类似于 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);
            }
        
        }
        

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-03-20
            • 1970-01-01
            • 1970-01-01
            • 2019-12-28
            • 1970-01-01
            • 1970-01-01
            • 2022-01-12
            相关资源
            最近更新 更多