@RestControllerAdvice с правильным ответом при исключении в Spring Boot?

Я реализовал глобальный обработчик исключений в своем приложении Spring Boot, следуя этому подходу.

Этот подход возвращает ошибку как тип ResponseEntity<ErrorResponse>, и вот класс ErrorResponse, как вы также можете видеть в этом примере GitHub:

Ответ об ошибке:

public class ErrorResponse {
    private final int status;
    private final String message;
    private String stackTrace;
    private List<String> errors;
}

С другой стороны, я реализую класс ApiResponse для форматирования ответа моего Controller, как показано ниже:

АпиОтвет:

public class ApiResponse<T> {

    private Long timestamp;
    private final String message;
    private final T data;

    public ApiResponse(Long timestamp, String message) {
        this.timestamp = timestamp;
        this.message = message;
        this.data = data;
    }
}

Вот мой метод контроллера, использующий этот


Контроллер:

@GetMapping("/units/{id}")
public ResponseEntity<ApiResponse<UnitResponse>> findById(@PathVariable long id) {
    final UnitResponse response = unitService.findById(id);
    return ResponseEntity.ok(
        new ApiResponse<>(Instant.now(clock).toEpochMilli(), "Success", response));
}

The problem is that, there are 2 different response types:
  • когда я возвращаю ответ от контроллера
  • когда обработчик исключений возвращает ответ.

Итак, я думаю, что мне следует объединить 2 типа ответов (ApiResponse и ErrorResponse). Это правда?

И я попытался использовать объединенный класс ответов вместо Object в GlobalExceptionHandler, но переопределенные методы выдают ошибку, поскольку реализованный метод возвращает Object.

Итак, как мне решить эту проблему и вернуть один и тот же тип ответа в каждом случае (когда есть ошибка или нет ошибки)?

Очень странно, что никто больше никогда не использовал такой же тип ответа за исключением.

rosy 04.02.2023 21:07

Правильно иметь разные типы ответа на ошибку (общий и легко обрабатываемый) и успех (конкретный для сделанного вызова). Ошибка обрабатывается клиентом для http 4xx и 5xx и конкретным ответом на вызов для Http 2xx.

John Williams 04.02.2023 21:57
The problem is that, there are 2 different response types так в чем именно проблема?
ILya Cyclone 04.02.2023 23:22

@JohnWilliams Спасибо за ваш ответ. Тогда мне не нужно объединять эти два типа ответов, я думаю. НО, у ErrorResponse есть status поле, а у ApiResponse нет. В этой ситуации, когда я проверяю ответ во внешнем интерфейсе, я думаю, что было бы неплохо проверить то же поле (например, статус). Верно? Если да, то должен ли я использовать одно и то же поле для обоих этих двух ответов, добавив поле status к ApiResponse? Что ты предлагаешь?

rosy 05.02.2023 08:19

У @ILyaCyclone ErrorResponse есть status поле, а у ApiResponse его нет. В этой ситуации, когда я проверяю ответ во внешнем интерфейсе, я думаю, что было бы неплохо проверить то же поле (например, статус). Верно? Если да, то должен ли я использовать одно и то же поле для обоих этих двух ответов, добавив поле status к ApiResponse? Что ты предлагаешь?

rosy 05.02.2023 08:20

Вам не нужно объединять классы ошибок и API. В FE вы должны проверять статус HTTP (developer.mozilla.org/en-US/docs/Web/API/Response/status). Если это хорошо (200, 201, 2xx), обработайте response.json() как ApiResponse. Если это плохо (400), тогда response.json() является ErrorResponse. Кстати: на чем написан клиент FE?

John Williams 05.02.2023 10:08

@JohnWilliams На самом деле я пытался проверить статус ответа с помощью Postman и не смог найти никаких данных о статусе в теле ответа, когда я вернусь ApiResponse. Итак, вы имеете в виду, что я могу получить статус, когда я проверяю внешний интерфейс, перейдя по ссылке, которую вы прислали? Фронтенд в React. Заранее спасибо.

rosy 05.02.2023 12:18

Статус http не находится в теле ответа - в почтальоне он отображается на панели навигации ответа - справа от вкладок "Body", "Cookie". Рядом с «Статусом»: Понимание (встроенного в протокол http) response.status имеет решающее значение для управления ответами об ошибках. Ссылка объясняет, что такое http.status. В React вы используете fetch() для вызова сервера? или что?

John Williams 05.02.2023 12:30

В React я использую fetch, но если есть возможность проверить статус ответа, то конечно можно и другими методами.

rosy 05.02.2023 12:38

@JohnWilliams Итак, вы имеете в виду, что подход, который я использую (опубликованный в моем вопросе), является правильным подходом, и я должен проверять статус ответа и управлять возвращенными данными на основе этого статуса ответа? Или вы могли бы предложить что-нибудь, например. добавить поле статуса в мой ApiResponse?

rosy 05.02.2023 16:05

Ваш подход правильный, но, чтобы быть ясным, не объединяйте apiresdponse с ответом об ошибке и не помещайте информацию об ошибке в apiresponse. Используйте response.status в клиенте для обработки хорошего (http-200) ответа (объект apiresponse) или ответа с ошибкой (http-400) (объект ошибки)

John Williams 05.02.2023 18:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
11
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не должны объединять классы ошибок и API. В FE вы должны проверять HTTP-статус. Если это хорошо (200, 201, 2xx), обработайте response.json() как ApiResponse. Если это плохо (400), тогда response.json() является ErrorResponse.

Это пример кода ReactJS, иллюстрирующий, как обрабатываются два ответа:

componentDidMount() {
    this.setState({ isLoading: true })
    let api_url = 'http://localhost:8080/units/' + '1'; // id is 1
    // Now, use JavaScript's native Fetch API to get
    // an ApiResponse<UnitResponse>
    fetch(api_url)
    .then(res => {
        if (res.status >= 400) {
            // unpack the error
            res.json().then(error => {
                // here you have the error
                console.error('Error returned is ', error);
            });
        }
        return res.json();
    })
    .then(apiResponse => {
        // here you have the ApiResponse<UnitResponse>>
        console.info('Response returned is ', apiResponse );
    },
    // Note: it's important to handle errors here 
    // instead of a catch() block so that we don't swallow
    // exceptions from actual bugs in components
    err => {
         // this is not the http.status type error
         // this is something like 'server not reached'
    });
}

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

Как мне исправить ошибку «Ожидается 0 аргументов, но найдено 3», если я хочу использовать аннотацию Lombok RequiredArgsConstructor?
Ошибка BeanCreationException при создании репозитория с Spring Boot — ошибка «Не управляемый тип»
Нет подходящего компонента типа «org.springframework.security.config.annotation.web.builders.HttpSecurity»
Как справиться с потоком в фоновой обработке java spring boot?
Не удается настроить загрузочный проект Spring в IntelliJ (не удается загрузить ошибку класса)
Spring Boot Multiple Database Configurations: избегать использования @Primary в одной из конфигураций базы данных
Класс Spring Boot Data не учитывает @NonNull или @NotNull, если существует конструктор по умолчанию
Свойства системы java против весенней загрузки application.properties против свойств maven
Как получить пользователей из базы данных, используя переменную, отличную от идентификатора пользователя
Не удалось создать пользовательский Spring Boot Starter/AutoConfiguration