Blogger Information
Blog 41
fans 0
comment 0
visits 25298
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
22、【网摘】SpringBoot全局异常处理
自由之上
Original
456 people have browsed it

在日常的 Web 开发中,会经常遇到大大小小的异常,此时往往需要一个统一的异常处理机制,来保证客户端能接收较为友好的提示。Spring Boot 同样提供了一套默认的异常处理机制,本节将对它进行详细的介绍。

1、Spring Boot 默认异常处理机制

Spring Boot 提供了一套默认的异常处理机制,一旦程序中出现了异常,Spring Boot 会自动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息。

1、对于浏览器客户端而言,Spring Boot 会响应一个“ whitelabel”错误视图,以 HTML 格式呈现错误信息,如图 1;

图1:Spring Boot 默认错误白页

2、对于机器客户端而言,Spring Boot 将生成 JSON 响应,来展示异常消息。

  1. {
  2. timestamp”: 2021-07-12T07:05:29.885+00:00”,
  3. status”: 404,
  4. error”: Not Found”,
  5. message”: No message available”,
  6. path”: “/m1ain.html
  7. }

2、Spring Boot 异常处理自动配置原理

Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了自动配置,该配置类向容器中注入了以下 4 个组件。

  • ErrorPageCustomizer:该组件会在在系统发生异常后,默认将请求转发到“/error”上。
  • BasicErrorController:处理默认的“/error”请求。
  • DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。
  • DefaultErrorAttributes:用于页面上共享异常信息。

下面,我们依次对这四个组件进行详细的介绍。

1、ErrorPageCustomizer

ErrorMvcAutoConfiguration 向容器中注入了一个名为 ErrorPageCustomizer 的组件,它主要用于定制错误页面的响应规则。

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

ErrorPageCustomizer 通过 registerErrorPages() 方法来注册错误页面的响应规则。当系统中发生异常后,ErrorPageCustomizer 组件会自动生效,并将请求转发到 “/error”上,交给 BasicErrorController 进行处理,其部分代码如下。

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

2、BasicErrorController

ErrorMvcAutoConfiguration 还向容器中注入了一个错误控制器组件 BasicErrorController,代码如下。

  1. @Bean
  2. @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
  3. public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
  4. ObjectProvider errorViewResolvers) {
  5. return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
  6. errorViewResolvers.orderedStream().collect(Collectors.toList()));
  7. }

BasicErrorController 的定义如下。

  1. //BasicErrorController 用于处理 “/error” 请求
  2. @Controller
  3. @RequestMapping(“KaTeX parse error: Expected '}', got 'EOF' at end of input: ver.error.path:{error.path:/error}}”)
  4. public class BasicErrorController extends AbstractErrorController {
  5. /**
  6. * 该方法用于处理浏览器客户端的请求发生的异常
  7. * 生成 html 页面来展示异常信息
  8. * @param request
  9. * @param response
  10. * @return
  11. /
  12. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  13. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  14. //获取错误状态码
  15. HttpStatus status = getStatus(request);
  16. //getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示
  17. Map<String, Object> model = Collections
  18. .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
  19. //为响应对象设置错误状态码
  20. response.setStatus(status.value());
  21. //调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容)
  22. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  23. return (modelAndView != null) ? modelAndView : new ModelAndView(“error”, model);
  24. }
  25. /*
  26. * 该方法用于处理机器客户端的请求发生的错误
  27. * 产生 JSON 格式的数据展示错误信息
  28. * @param request
  29. * @return
  30. */
  31. @RequestMapping
  32. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  33. HttpStatus status = getStatus(request);
  34. if (status == HttpStatus.NO_CONTENT) {
  35. return new ResponseEntity<>(status);
  36. }
  37. Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
  38. return new ResponseEntity<>(body, status);
  39. }
  40. }

Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。

换句话说,当使用浏览器访问出现异常时,会进入 BasicErrorController 控制器中的 errorHtml() 方法进行处理,当使用安卓、IOS、Postman 等机器客户端访问出现异常时,就进入error() 方法处理。

在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法,代码如下。

  1. protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
  2. Map<String, Object> model) {
  3. //获取容器中的所有的错误视图解析器来处理该异常信息
  4. for (ErrorViewResolver resolver : this.errorViewResolvers) {
  5. //调用错误视图解析器的 resolveErrorView 解析到错误视图页面
  6. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
  7. if (modelAndView != null) {
  8. return modelAndView;
  9. }
  10. }
  11. return null;
  12. }

从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。

3、DefaultErrorViewResolver

ErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 DefaultErrorViewResolver,代码如下。

  1. @Bean
  2. @ConditionalOnBean(DispatcherServlet.class)
  3. @ConditionalOnMissingBean(ErrorViewResolver.class)
  4. DefaultErrorViewResolver conventionErrorViewResolver() {
  5. return new DefaultErrorViewResolver(this.applicationContext, this.resources);
  6. }

当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调用它们的 resolveErrorView() 方法对异常信息进行解析,其中自然也包括 DefaultErrorViewResolver(默认错误信息解析器)。

DefaultErrorViewResolver 的部分代码如下。

  1. public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  2. private static final Map<HttpStatus.Series, String> SERIES_VIEWS;
  3. static {
  4. Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
  5. views.put(Series.CLIENT_ERROR, 4xx”);
  6. views.put(Series.SERVER_ERROR, 5xx”);
  7. SERIES_VIEWS = Collections.unmodifiableMap(views);
  8. }
  9. @Override
  10. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  11. //尝试以错误状态码作为错误页面名进行解析
  12. ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
  13. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  14. //尝试以 4xx 或 5xx 作为错误页面页面进行解析
  15. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  16. }
  17. return modelAndView;
  18. }
  19. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  20. //错误模板页面,例如 error/404、error/4xx、error/500、error/5xx
  21. String errorViewName = error/” + viewName;
  22. //当模板引擎可以解析这些模板页面时,就用模板引擎解析
  23. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
  24. this.applicationContext);
  25. if (provider != null) {
  26. //在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图
  27. return new ModelAndView(errorViewName, model);
  28. }
  29. //若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面
  30. return resolveResource(errorViewName, model);
  31. }
  32. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  33. //遍历所有静态资源文件夹
  34. for (String location : this.resources.getStaticLocations()) {
  35. try {
  36. Resource resource = this.applicationContext.getResource(location);
  37. //静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
  38. resource = resource.createRelative(viewName + “.html”);
  39. //若静态资源文件夹下存在以上错误页面,则直接返回
  40. if (resource.exists()) {
  41. return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
  42. }
  43. } catch (Exception ex) {
  44. }
  45. }
  46. return null;
  47. }
  48. }

DefaultErrorViewResolver 解析异常信息的步骤如下:

  1. 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。
  2. 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。
  3. 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。
  4. 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。

4、DefaultErrorAttributes

ErrorMvcAutoConfiguration 还向容器中注入了一个组件默认错误属性处理工具 DefaultErrorAttributes,代码如下。

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

DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理工具,它可以从请求中获取异常或错误信息,并将其封装为一个 Map 对象返回,其部分代码如下。

  1. public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
  2. @Override
  3. public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
  4. Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
  5. if (!options.isIncluded(Include.EXCEPTION)) {
  6. errorAttributes.remove(“exception”);
  7. }
  8. if (!options.isIncluded(Include.STACK_TRACE)) {
  9. errorAttributes.remove(“trace”);
  10. }
  11. if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get(“message”) != null) {
  12. errorAttributes.remove(“message”);
  13. }
  14. if (!options.isIncluded(Include.BINDING_ERRORS)) {
  15. errorAttributes.remove(“errors”);
  16. }
  17. return errorAttributes;
  18. }
  19. private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  20. Map<String, Object> errorAttributes = new LinkedHashMap<>();
  21. errorAttributes.put(“timestamp”, new Date());
  22. addStatus(errorAttributes, webRequest);
  23. addErrorDetails(errorAttributes, webRequest, includeStackTrace);
  24. addPath(errorAttributes, webRequest);
  25. return errorAttributes;
  26. }
  27. }

在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:

  • timestamp:时间戳;
  • status:错误状态码
  • error:错误的提示
  • exception:导致请求处理失败的异常对象
  • message:错误/异常消息
  • trace: 错误/异常栈信息
  • path:错误/异常抛出时所请求的URL路径

所有通过 DefaultErrorAttributes 封装到 model 数据中的属性,都可以直接在页面或 JSON 中获取。


加入
QQ群:722461036
微信群:
一起督促、学习、练习、温习、复习 ~ ~ ~

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post