博客列表 >22、【网摘】SpringBoot全局异常处理

22、【网摘】SpringBoot全局异常处理

自由之上
自由之上原创
2022年11月14日 09:39:43455浏览

在日常的 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
微信群:
一起督促、学习、练习、温习、复习 ~ ~ ~

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议