Spring Remoting HTTP invoker - обработка исключений - PullRequest
2 голосов
/ 12 марта 2012

Я использую Решение удаленного взаимодействия HTTP Invoker ', созданное Spring , чтобы предоставлять DAO множеству различных приложений, но иметь доступ ко всем базам данных на одном сервере.

Это работает хорошо, но если сервер генерирует, скажем, HibernateSystemException, Spring сериализует это и отправляет его по сети обратно клиенту. Это не работает, потому что клиент не имеет (и не должен) иметь HibernateSystemException в своем пути к классам.

Может ли быть способ заставить Spring Remoting обернуть мое исключение во что-то, что я укажу, что будет общим для клиента и сервера, чтобы избежать подобных проблем?

Я знаю, что мог бы сделать это в своем серверном коде, обернув все, что DAO делает, в try / catch, но это, по общему признанию, небрежно.

Спасибо, Рой

Ответы [ 5 ]

4 голосов
/ 30 апреля 2012

Я тоже столкнулся с этой проблемой;Я предоставляю сервис через 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>
0 голосов
/ 09 июля 2015

Я использовал AspectJ для переноса исключения, но он не работает для исключения, возникающего в Spring прокси, например, аннотация @Transactional при сбое подключения к базе данных. Однако метод setInterceptor в RmiServiceExporter работает отлично.

0 голосов
/ 12 мая 2014

Я использовал решение, похожее на 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);
    }

}
0 голосов
/ 12 марта 2012

Я бы сказал, что попытка / улов в слое Facade перед вашими DAO - это ровно , что вы хотите, чтобы получить полный контроль над исключениями, которые вы возвращаете.Я согласен, что на первый взгляд это кажется уродливым, но это жизненно важный уровень между клиентом и DAO, по моему мнению.

Вы могли бы даже вернуть какой-либо объект OperationStatus, вместо того, чтобы использовать пустые типы возврата, чтобы передать оба результата (сработало, нет) и сообщение об ошибке для вызовов API данных хранилища.

0 голосов
/ 12 марта 2012

Я понимаю, что вы не хотите, чтобы ваши клиенты имели HibernateSystemException в своем пути к классам, но я бы сказал, что они должны, если вы правильно используете HTTPInvoker. Он не предназначен для того, чтобы быть фасадом службы / уровнем интерфейса: все, что он должен сделать, это позволить вам запускать методы Java на удаленной JVM, используя HTTP вместо RMI.

Так что, если вы действительно не хотите, чтобы клиенты зависели от Hibernate, ваш блок try / catch - это то, что нужно. (Хотя я бы поспорил с этим, поскольку отладка будет затруднена: ваша трассировка стека теперь будет разделена между клиентом и сервером).

Я не использовал его сам, но вы можете попробовать метод org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[]), чтобы добавить аспект для отлова этого конкретного исключения только в одном месте, вместо добавления повтора / перехвата повсюду.

...