1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/GiteeOS-springboot-guide

Клонировать/Скачать
springboot-handle-exception-plus.md 13 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.06.2025 10:20 f496caa

Эта статья задержалась очень долго. В предыдущей статье «Как правильно использовать перечисления в Java, это не так просто!» я упомянул, что собираюсь поделиться. Только после того, как один из читателей напомнил мне об этом, я обнаружил, что забыл опубликовать её на канале. В этом месте я обнаружил, что у меня есть большая проблема: иногда я говорю, что обновлю что-то, но потом забываю об этом. Часто это не потому, что я не написал, а потому что забываю из-за множества других дел. В будущем я постараюсь исправить эту проблему!

В предыдущей статье «Несколько способов обработки исключений в Spring Boot» я представил:

  1. Использование @ControllerAdvice и @ExceptionHandler для обработки глобальных исключений
  2. @ExceptionHandler для обработки исключений на уровне контроллера
  3. ResponseStatusException

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

Конечный результат

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

Конечный результат

Возвращенная информация включает в себя 5 частей:

  1. Уникальный код, идентифицирующий исключение
  2. HTTP-статусный код
  3. Путь ошибки
  4. Временная метка, когда произошла ошибка
  5. Конкретная информация об ошибке

Такой способ возврата информации об исключениях позволяет фронтенду лучше отображать ошибки.

Основной код обработки исключений ErrorCode.java (этот перечисление содержит уникальные идентификаторы исключения, HTTP-статусные коды и ошибки)

Основная роль этого класса — унифицированное управление исключениями, которые могут возникнуть. Однако, когда система становится слишком сложной, а количество исключений увеличивается, этот класс может стать слишком большим. Одним из решений может быть объединение похожих исключений в один, например, объединение исключения "пользователь не найден" и исключения "информация о заказе не найдена" в одно исключение "ресурс не найден", а затем фронтенд может обрабатывать его более детально в зависимости от конкретной ситуации (это моя личная методика обработки, и я не гарантирую, что это оптимальное решение).

import org.springframework.http.HttpStatus;

public enum ErrorCode {

    RESOURCE_NOT_FOUND(1001, HttpStatus.NOT_FOUND, "Ресурс не найден"),
    REQUEST_VALIDATION_FAILED(1002, HttpStatus.BAD_REQUEST, "Ошибка валидации запроса");
}
```## Класс `ErrorReponse`

```java
public ErrorReponse(int code, int status, String message, String path, Map<String, Object> data) {
    this.code = code;
    this.status = status;
    this.message = message;
    this.path = path;
    this.timestamp = Instant.now();
    if (!ObjectUtils.isEmpty(data)) {
        this.data.putAll(data);
    }
}

@Override
public String toString() {
    return "ErrorReponse{" +
            "code=" + code +
            ", status=" + status +
            ", message='" + message + '\'' +
            ", path='" + path + '\'' +
            ", timestamp=" + timestamp +
            ", data=" + data +
            '}';
}

Класс BaseException

Абстрактный класс, который является родительским для всех исключений в системе.```java public abstract class BaseException extends RuntimeException { private final ErrorCode error; private final HashMap<String, Object> data = new HashMap<>();

public BaseException(ErrorCode error, Map<String, Object> data) {
    super(error.getMessage());
    this.error = error;
    if (!ObjectUtils.isEmpty(data)) {
        this.data.putAll(data);
    }
}

protected BaseException(ErrorCode error, Map<String, Object> data, Throwable cause) {
    super(error.getMessage(), cause);
    this.error = error;
    if (!ObjectUtils.isEmpty(data)) {
        this.data.putAll(data);
    }
}

public ErrorCode getError() {
    return error;
}

public Map<String, Object> getData() {
    return data;
}

}


### Класс `ResourceNotFoundException`

Пользовательский класс исключения, который наследуется от `BaseException`.

```java
public class ResourceNotFoundException extends BaseException {

    public ResourceNotFoundException(Map<String, Object> data) {
        super(ErrorCode.RESOURCE_NOT_FOUND, data);
    }
}

Класс GlobalExceptionHandler

Глобальный обработчик исключений, который определяет два метода обработки исключений.

// Определение метода обработки исключений
public class GlobalExceptionHandler {

    // Обработка исключения
    public ResponseEntity<ErrorResponse> handleAppException(AppException ex, WebRequest request) {
        // Логика обработки исключения
    }

    // Обработка исключения
    public ResponseEntity<ErrorResponse> handleException(Exception ex, WebRequest request) {
        // Логика обработки исключения
    }
}
```В этом классе метод `handleAppException()` достаточно для обработки всех исключений, так как все исключения в системе наследуются от `BaseException`.
```java
import com.twuc.webApp.web.ExceptionController;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
```@ControllerAdvice(assignableTypes = {ExceptionController.class})
@ResponseBody
public class GlobalExceptionHandler {
    
    // Также можно заменить BaseException на RuntimeException
    // Поскольку RuntimeException является родительским классом для BaseException
    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
        ErrorReponse representation = new ErrorReponse(ex, request.getRequestURI());
        return new ResponseEntity<>(representation, new HttpHeaders(), ex.getError().getStatus());
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
        ErrorReponse errorReponse = new ErrorReponse(ex, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorReponse);
    }
}

(Важно) дополнительный вопрос:

Ха-ха! На самом деле я добавил дополнительный метод обработки исключений handleResourceNotFoundException(), чтобы проверить, какой из методов будет обрабатывать исключение ResourceNotFoundException.

Ответ:

Исключение ResourceNotFoundException будет обработано методом handleResourceNotFoundException(). В процессе обработки исключений с помощью @ExceptionHandler, Spring ищет наиболее подходящий метод обработки исключения.

Вот краткий анализ исходного кода:

В данном коде представлен класс GlobalExceptionHandler, который используется для глобального обработчика исключений. Он содержит два метода обработки исключений: handleAppException() и handleResourceNotFoundException(). Метод handleAppException() обрабатывает исключения типа BaseException, а метод handleResourceNotFoundException() обрабатывает исключения типа ResourceNotFoundException.``````markdown @Nullable private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<>(); // Находит все异常类型,mappedMethods 中存放了异常和处理异常的方法的对应关系 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } // 不为空说明有方法处理异常 if (!matches.isEmpty()) { // 按照匹配程度从小到大排序 matches.sort(new ExceptionDepthComparator(exceptionType)); // 返回处理异常的方法 return this.mappedMethods.get(matches.get(0)); } else { return null; } }

```Из исходного кода следует: **`getMappedMethod()` сначала находит все методы, которые могут соответствовать обработке исключения, затем сортирует их по возрастанию, и в конце выбирает метод с наименьшим значением (то есть наиболее подходящий).**``

Из исходного кода следует: **`getMappedMethod()` сначала находит все методы, которые могут соответствовать обработке исключения, затем сортирует их по возрастанию, и в конце выбирает метод с наименьшим значением (то есть наиболее подходящий).**## Напишите класс для тестирования выбрасывания исключения

**`Person.java`**

```java
public class Person {
    private Long id;
    private String name;

    // Пропущены методы getter и setter
}

ExceptionController.java (класс, выбрасывающий исключение)

@RestController
@RequestMapping("/api")
public class ExceptionController {

    @GetMapping("/resourceNotFound")
    public void throwException() {
        Person p = new Person(1L, "SnailClimb");
        throw new ResourceNotFoundException(ImmutableMap.of("person id:", p.getId()));
    }

}

Исходный код доступен по адресу: https://github.com/Snailclimb/springboot-guide/tree/master/source-code/basis/springboot-handle-exception-improved

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/GiteeOS-springboot-guide.git
git@api.gitlife.ru:oschina-mirror/GiteeOS-springboot-guide.git
oschina-mirror
GiteeOS-springboot-guide
GiteeOS-springboot-guide
master