Подробно объясните обработку исключений Spring Boot по умолчанию (рекомендуемая коллекция)

Spring Boot Java задняя часть

Эта статья участвует"Проект "Звезда раскопок"", чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

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

Механизм обработки исключений Spring Boot по умолчанию

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

  1. Для клиента браузера Spring Boot отвечает представлением ошибки «белой метки», отображая сообщение об ошибке в формате HTML, как показано на рисунке.

Рисунок 1: Белая страница ошибки Spring Boot по умолчанию

  1. Для машин-клиентов Spring Boot сгенерирует ответ JSON для отображения сообщения об исключении.
{
    "timestamp": "2021-07-12T07:05:29.885+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/m1ain.html"
}

Принцип автоматической настройки обработки исключений Spring Boot

Spring Boot обеспечивает автоматическую настройку обработки исключений с помощью класса конфигурации ErrorMvcAutoConfiguration, который внедряет в контейнер следующие 4 компонента.

  • ErrorPageCustomizer: этот компонент по умолчанию перенаправляет запрос в «/ error» после возникновения исключения в системе.
  • BasicErrorController: обрабатывает запрос «/ error» по умолчанию.
  • DefaultErrorViewResolver: Преобразователь представления ошибок по умолчанию, который анализирует информацию об исключении для соответствующего представления ошибок.
  • DefaultErrorAttributes: используется для обмена информацией об исключении на странице.

Ниже мы подробно по очереди представим эти четыре компонента.

ErrorPageCustomizer

ErrorMvcAutoConfiguration внедряет в контейнер компонент с именем ErrorPageCustomizer, который в основном используется для настройки правил ответа страниц ошибок.

@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
    return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}

ErrorPageCustomizer использует метод registerErrorPages() для регистрации правил ответа страницы ошибок. Когда в системе возникает исключение, компонент ErrorPageCustomizer автоматически вступает в силу и перенаправляет запрос в «/ error» и передает его в BasicErrorController для обработки.Часть кода выглядит следующим образом.

@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
    //将请求转发到 /errror(this.properties.getError().getPath())上
    ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
    // 注册错误页面
    errorPageRegistry.addErrorPages(errorPage);
}

BasicErrorController

ErrorMvcAutoConfiguration также внедряет в контейнер компонент контроллера ошибок BasicErrorController, код выглядит следующим образом.

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                 ObjectProvider<ErrorViewResolver> errorViewResolvers) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
            errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

BasicErrorController определяется следующим образом.

//BasicErrorController 用于处理 “/error” 请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    ......
    /**
     * 该方法用于处理浏览器客户端的请求发生的异常
     * 生成 html 页面来展示异常信息
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        //获取错误状态码
        HttpStatus status = getStatus(request);
        //getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示
        Map<String, Object> model = Collections
                .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        //为响应对象设置错误状态码
        response.setStatus(status.value());
        //调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容)
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    /**
     * 该方法用于处理机器客户端的请求发生的错误
     * 产生 JSON 格式的数据展示错误信息
     * @param request
     * @return
     */
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        return new ResponseEntity<>(body, status);
    }
    ......
}
Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。
тип возвращаемого значения объявление метода Тип клиента Тип возвращаемого сообщения об ошибке
ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) браузерный клиент text/html (страница ошибки)
ResponseEntity<Map<String, Object>> error(HttpServletRequest request) Машинные клиенты (например, Android, IOS, Postman и т. д.) JSON

Другими словами, когда возникает исключение при использовании браузера для доступа, оно вводит метод errorHtml() в контроллере BasicErrorController для обработки.Когда исключение возникает при использовании машинного клиента, такого как Android, IOS, Postman и т. д., он войдет в метод error() для обработки.

В методе errorHtml() будет вызываться метод resolveErrorView() родительского класса (AbstractErrorController) Код выглядит следующим образом.

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
                                        Map<String, Object> model) {
    //获取容器中的所有的错误视图解析器来处理该异常信息
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        //调用错误视图解析器的 resolveErrorView 解析到错误视图页面
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}
从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。

DefaultErrorViewResolver

ErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 DefaultErrorViewResolver,代码如下。
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}

Когда клиент, делающий запрос, является браузером, Spring Boot получит все объекты ErrorViewResolver (преобразователи представления ошибок) в контейнере и вызовет их метод resolveErrorView() для разрешения информации об исключении, которая, естественно, также включает DefaultErrorViewResolver (представление ошибок по умолчанию). преобразователь) парсер сообщений об ошибках).

Часть кода DefaultErrorViewResolver выглядит следующим образом. 

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

    private static final Map<HttpStatus.Series, String> SERIES_VIEWS;

    static {
        Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
    }

    ......

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        //尝试以错误状态码作为错误页面名进行解析
        ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            //尝试以 4xx 或 5xx 作为错误页面页面进行解析
            modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //错误模板页面,例如 error/404、error/4xx、error/500、error/5xx
        String errorViewName = "error/" + viewName;
        //当模板引擎可以解析这些模板页面时,就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                this.applicationContext);
        if (provider != null) {
            //在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图
            return new ModelAndView(errorViewName, model);
        }
        //若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面
        return resolveResource(errorViewName, model);
    }

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        //遍历所有静态资源文件夹
        for (String location : this.resources.getStaticLocations()) {
            try {
                Resource resource = this.applicationContext.getResource(location);
                //静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
                resource = resource.createRelative(viewName + ".html");
                //若静态资源文件夹下存在以上错误页面,则直接返回
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception ex) {
            }
        }
        return null;
    }
    ......
}

Шаги для DefaultErrorViewResolver для разрешения информации об исключении следующие:

  1. На основе кода состояния ошибки (например, 404, 500, 400 и т. д.) сгенерируйте ошибку/статус представления ошибки, например, ошибка/404, ошибка/500, ошибка/400.
  2. Попробуйте использовать механизм шаблонов для анализа представления об ошибке/статусе, то есть попробуйте найти error/status.html в каталоге шаблонов в пути к классам, например error/404.html, error/500.html, error/400. .html.
  3. Если обработчик шаблонов может анализировать представление об ошибке/состоянии, инкапсулируйте представление и данные в ModelAndView, чтобы вернуть и завершить весь процесс анализа, в противном случае перейдите к шагу 4.
  4. Найдите файл error/status.html в каждой папке статических ресурсов по очереди. Если страница с ошибкой найдена в папке статических ресурсов, вернитесь и завершите весь процесс синтаксического анализа, в противном случае перейдите к шагу 5.
  5. Преобразуйте код состояния ошибки (например, 404, 500, 400 и т. д.) в 4xx или 5xx, а затем повторите первые 4 шага, если синтаксический анализ прошел успешно, вернитесь и завершите весь процесс синтаксического анализа, в противном случае перейдите к шагу 6. 
  6. Обработайте запрос «/error» по умолчанию, используя страницу ошибок Spring Boot по умолчанию (страница ошибок Whitelabel).

DefaultErrorAttributes

ErrorMvcAutoConfiguration также внедряет в контейнер средство обработки атрибутов ошибок по умолчанию DefaultErrorAttributes, код выглядит следующим образом.


@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}

DefaultErrorAttributes — это инструмент обработки атрибутов ошибок Spring Boot по умолчанию. Он может получать информацию об исключении или ошибке из запроса, инкапсулировать ее в виде объекта Map и возвращать ее. Часть кода выглядит следующим образом.

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
......
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}

private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
......
}
在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:
  • отметка времени: отметка времени;
  • статус: код статуса ошибки
  • ошибка: сообщение об ошибке
  • исключение: объект исключения, вызвавший сбой обработки запроса.
  • сообщение: сообщение об ошибке/исключении
  • трассировка: информация о стеке ошибок/исключений
  • путь: URL-адрес, запрошенный при возникновении ошибки/исключения.

Все атрибуты, инкапсулированные в данные модели через DefaultErrorAttributes, можно получить непосредственно на странице или в формате JSON.

Хорошо, давайте сегодня поделимся этим здесь, я Сяо Ао, увидимся в следующем выпуске~~