Практика SpringBoot — обработка исключений в фильтре и обработка исключений в контроллере

Spring Boot программист

В этой статье в основном описывается, как использовать возможности интерфейса ErrorController, предоставляемые SpringBoot; он имеет встроенный BasicErrorController для единообразной обработки исключений. Когда в контроллере возникает исключение, он автоматически перенаправляет запрос на путь запроса /error ( /error — это сопоставление по умолчанию, предоставляемое SpringBoot). BasicErrorController выдает две ошибки возврата: 1. возврат страницы, 2. возврат json.

задний план

При разработке возникла проблема: все остальные запросы в проекте возвращаются в виде json, а унифицированный объект структуры данных настроен следующим образом:

public class Response<T> {
    // 数据
    private T data;
    // success 标记
    private boolean success;
    // 异常信息
    private String error;
    // 省略 get set
}

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

@RequestMapping("test")
public Response<String> testApi(){
    Response<String> result = new Response<>();
    result.setData("this is glmapper blog");
    result.setSuccess(true);
    return result;
}

По сути, это упрощенная версия модели; из соображений безопасности теперь требуется проверять каждый запрос, например проверять, содержит ли запрос токен. Идея очень проста — перехватывать и проверять запросы с помощью перехватчиков или фильтров.

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

Напишите ответ прямо обратно

Используйте PrintWriter, предоставленный в ServletResponse, чтобы напрямую распечатать ответ в формате json. Примерный код выглядит следующим образом:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    String requestURI = request.getRequestURI();
    // mock 测试异常请求
    if (requestURI.contains("testTokenError")) {
        Response<String> response = new Response<>();
        response.setError("token validation fails");
        // 回写异常信息
        returnResponse((HttpServletResponse)servletResponse,JSONObject.toJSONString(response));
        // 返回
        return;
    }
    chain.doFilter(servletRequest, servletResponse);
}

private void returnResponse(HttpServletResponse response, String data) {
    PrintWriter writer = null;
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html; charset=utf-8");
    try {
        writer = response.getWriter();
        // 通过 PrintWriter 将 data 数据直接 print 回去
        writer.print(data);
    } catch (IOException e) {
    } finally {
        if (writer != null)
            writer.close();
    }
}

Этот метод относительно прост и понятен.После печати аномальных данных вернитесь напрямую и не продолжайте цепочку фильтров.

Сгенерировать исключение и обработать его с помощью BasicErrorController.

Этот метод использует возможности, предоставляемые самой SpringBoot, что позволяет более изящно обрабатывать сообщения об ошибках. Код примерно такой:

1. Выдает исключение прямо в фильтре

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    String requestURI = request.getRequestURI();
    // mock 测试异常请求
    if (requestURI.contains("testTokenError")) {
        // 直接返回一个自定义的异常
        throw new ValidationException("token validation fails");
    }
    chain.doFilter(servletRequest, servletResponse);
}

2. Определите контроллер обработки исключений

Определите здесь TokenErrorController, наследуйте от класса BasicErrorController, предоставленного SpringBoot, а затем переопределите метод ошибки (если это страница, переопределите метод errorHtml) для возврата пользовательских данных ответа. код показывает, как показано ниже:

@RestController
public class TokenErrorController extends BasicErrorController {
    // 重写 error 方法
    @Override
    @RequestMapping(produces = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
            isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        // 拿到 body 中的异常 message 
        String message = body.get("message").toString();
        // 构建 Response 对象
        Response response = new Response();
        // 将 message 的 设置到 response 
        response.setError(message);
        // 返回
        return new ResponseEntity(response, status);
    }
    // 省略其他无关代码
}

Это позволяет обрабатывать только исключения, созданные в дополнительных фильтрах, без изменения какого-либо кода в предыдущем проекте. Следует отметить, что описанное выше предназначено для принятия информации об исключении, созданной фильтром, через BasicErrorController, а затем для переноса и возврата информации об исключении через BasicErrorController. Зачем это поднимать? Основная цель состоит в том, чтобы отличить его от двух аннотаций, предоставляемых уровнем запроса REST в SpringBoot для обработки глобальных исключений.Эти две аннотации — @ControllerAdvice и @RestControllerAdvice.Из названия аннотации видно, что в SpringBoot вы можете Эти две аннотации используются для реализации глобального перехвата классов, помеченных @Controller и @RestController.Поскольку это перехват АОП на уровне контроллера, для исключений, созданных в фильтре, глобальные исключения, определенные аннотациями @ControllerAdvice и @RestControllerAdvice Процессор не могу справиться с этим.

Ниже приведено краткое введение в использование двух аннотаций @ControllerAdvice и @RestControllerAdvice.

глобальная обработка исключений

Настройте OtherExcepetion, а затем используйте аннотацию @RestControllerAdvice, чтобы написать глобальный обработчик исключений.

@RestControllerAdvice
public class OtherExceptionHandler {
    // 这里只处理 OtherException 异常类型
    @ExceptionHandler(value = OtherException.class)
    public Response<String> otherExceptionHandler(HttpServletRequest req, OtherException e){
        Response response = new Response();
        response.setError(e.getMessage());
        return response;
    }
    // 当然你也可以定义处理其他异常的 @ExceptionHandler
}

Этот метод не может обрабатывать исключения в фильтре, а только исключения, создаваемые в контроллере.

резюме

В этой статье в основном описывается, как гарантировать, что исключение, созданное в фильтре, может быть возвращено в объекте того же типа, что и бизнес в SpringBoot, и кратко представлена ​​обработка захвата исключений на основе контроллера, предоставляемая в SpringBoot. Два метода обработки исключений различны:

  • BasicErrorController: принять обработку запроса на исключение из /error, исключение, созданное в фильтре, сначала перенаправляется в /error, а затем обрабатывается.
  • @RestControllerAdvice: выполняя перехват AOP для всех классов, отмеченных аннотациями @Controller, можно сопоставить определенные обработчики исключений для обработки в соответствии с типами исключений.

Уровень ограничен, если статья неверна, надеюсь, вы меня поправите~