Подробное объяснение неспособности Dubbo обрабатывать пользовательские исключения и решения.

Dubbo

описание проблемы

У Dubbo странная проблема.Не знаю какие соображения у Apache и Alibaba на данный момент.Похоже,более подходящего решения не было никогда.Проблема в следующем:

  • При построении проекта вам необходимо настроить локальное исключение с именем, например, BusinessException. Более общий код записи выглядит следующим образом:

    /**
     * @Author linqiang
     * @Date 2019/10/24 16:20
     * @Version 1.0
     * @Description 业务异常类
     **/
    public class BusinessException extends RuntimeException {
        private Integer code;
        private String msg;
    
        public BusinessException(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }
    
  • Обычно это BusinessException должно использоваться между модулями, обычно помещаемыми в модуль commons или core, а файлы pom.xml других модулей представляют эти модули, так что весь проект может использовать это BusinessException.

  • Проблема в том, что если модуль B вызывается в модуле A, а модуль B генерирует BusinessException, модуль A получает не BusinessException, а RuntimeException, и детали BusinessException полностью теряются, остается только одно описание A имя класса.

проблема вызывает

О причинах этой проблемы см.эта статьяПодводя итог, в процессе передачи информации Dubbo класс ExceptionFilter.java будет фильтровать Exception, код ключа фильтра следующий:

// directly throw if it's checked exception
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
    return;
}
// directly throw if the exception appears in the signature
try {
    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
    Class<?>[] exceptionClassses = method.getExceptionTypes();
    for (Class<?> exceptionClass : exceptionClassses) {
        if (exception.getClass().equals(exceptionClass)) {
            return;
        }
    }
} catch (NoSuchMethodException e) {
    return;
}

// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
    return;
}
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
    return;
}

// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
    return;
}

// otherwise, wrap with RuntimeException and throw back to the client
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;

То есть Dubbo будет обрабатывать это так, когда столкнется с исключением:

  • Non-RuntimeException не обрабатывается и возвращается напрямую
  • Выдает исключение, указанное в методе, и возвращает результат напрямую
  • Если класс исключения и класс интерфейса находятся в одном пакете jar, вернитесь напрямую
  • Класс исключения в каталоге java или javax возвращается напрямую.
  • Собственное RpcException Dubbo, возврат напрямую
  • Другие исключения будут инкапсулированы и возвращены как RuntimeException.

Решение

В соответствии с приведенным выше анализом очевидно, что пользовательское исключение инкапсулируется напрямую и возвращается как RuntimeException, и включается только информация об имени класса пользовательского исключения, а другие детали теряются.

Затем мы хотим настроить исключение для нормального возврата, только если выполняются указанные выше условия, написанные этим ФИЛЬТРОМ. Мы можем проанализировать:

  • Не наследуется от RuntimeException для проверки возникновения исключений.Не рекомендуется, обычные бизнес-исключения должны быть исключениями во время выполнения.

  • Write вызывает исключение BusinessException для метода интерфейса следующим образом:

    public interface DemoService {
    
        DemoUser getUserInfo(Long userID) throws BusinessException;
    
    }
    

    Это не рекомендуется, это не соответствует принципам дизайна интерфейса, и это означает, что исключения во время выполнения рассматриваются как проверенные исключения.

  • Поместите пользовательские классы исключений и интерфейсы в один и тот же каталог пакетов.Не рекомендуется, ведь это равносильно привязке каталога класса исключений, и сцепление становится выше.

  • Измените имя пакета, чтобы оно начиналось с «java» или «javax.».Не рекомендуется, нарушает принцип именования классов.

  • Наследовать RpcException от Dubbo. RpcException также наследует RuntimeException, поэтому с ним можно обращаться так же, как с RuntimeException.Не рекомендуется, это эквивалентно пользовательскому исключению, принадлежащему Dubbo RpcException, что неразумно с точки зрения дизайна программы.

Мы обнаружили, что для того, чтобы удовлетворить условию, что фильтр Dubbo непосредственно возвращает исключение, мы должны сделать некоторые операции, которые нарушают дизайн программы.Если мы должны выбрать один из этих методов, условно говоря,Пользовательские классы исключений и интерфейсы размещаются в одном каталоге, а RpcException наследуетсяявляется менее инвазивным способом программирования.

Другие решения

Ссылаться наэта статья, предлагает два решения:

1. Настройте в конфигурационном файле, как показано ниже, результат: закройте ExceptionFIlter, чтобы все исключения обходили фильтр и возвращались напрямую.Это не рекомендуется. Поскольку Dubbo настроил этот класс фильтрации исключений, это должно быть сделано из соображений безопасности и функциональности. Непосредственное его отключение может вызвать другие проблемы.

dubbo:
  provider:
    filter: -exception

2. Измените ExceptionFilter исходного файла Dubbo, чтобы он мог возвращаться непосредственно при обнаружении BusinessException.Не рекомендуется, это эквивалентно настройке локального пакета Dubbo, что представляет собой большую яму, которую легко упустить из виду в дальнейшем.

Суммировать

Когда Dubbo обрабатывает пользовательские исключения, он напрямую возвращает RuntimeException и стирает все детали пользовательских исключений, делая невозможным их обработку.

На момент написания этой статьи версия Dubbo — 2.7.3, и идеального решения этой проблемы не существует.Пользовательские исключения и классы интерфейса размещаются в одном каталоге.является наименее инвазивным вариантом.