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[]) метод, чтобы добавить аспект, чтобы поймать это конкретное исключение только в одном месте, вместо добавления повтора / ловли повсюду.

Другие вопросы по тегам