SpringBoot 异常处理机制详解
异常处理自动配置类 ErrorMvcAutoConfiguration
SpringBoot 异常处理的自动配置类是 ErrorMvcAutoConfiguration
,本节将说明该自动配置类中配置了哪些重要组件,以及这些组件有什么作用。
概览
在深入介绍 ErrorMvcAutoConfiguration
所注册的组件前,这里先做一个概览,主要介绍其中一些重要的组件。
BasicErrorController
错误处理控制器:
默认对 /error
错误请求进行处理并响应。
SpringBoot 各式各样的异常处理,最后都是发送 /error
请求,从而通过 BasicErrorController
中的两个处理器来响应错误页面或者响应错误数据。除非你自定义了 ErrorController
类并注册到容器中。
DefaultErrorAttributes
默认错误属性:
主要功能是规定了错误处理最后对外响应,应该响应哪些信息,比如规定了,要响应时间戳,状态码,错误信息,错误堆栈等,无论是响应错误页面还是响应错误数据。
DefaultErrorViewResolver
默认错误视图解析器:
相应错误页面时,对错误视图名进行解析并得到错误视图的一种策略:返回错误码对应的视图,或者错误码对应的静态 html 页面。
StaticView
默认静态错误视图:
响应错误页面时,错误视图名为 error
的情况下对应的最基本的默认错误视图。该视图渲染出来是一个白页。
BeanNameViewResolver
Bean 命名视图解析器:
响应错误页面时,对错误视图名进行解析并得到错误视图的一种策略:在容器中寻找 id 为错误视图名的组件,若找到检查该组件是否为视图,如果是就将其返回,解析完成。
其实 BeanNameViewResolver
并不是一个错误视图解析器,但却主要用在错误视图解析的场景下,而且一般都是配合上面的 StaticView
来使用。因为一般来说,容器中只会存放 StaticView
这么一个名为 error
的 View
视图对象,从而可以被解析到。
BasicErrorController
错误处理控制器
BasicErrorController
是异常处理机制下,最核心最关键的组件。
一般情况下,我们不会自定义 ErrorController
和 ErrorAttributes
来全面接管 SpringBoot 的异常处理机制,所以以下自动配置将会执行:
1 2 3 4 5 6 7
| @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())); }
|
自动配置会从容器中取出 ErrorAttributes
,以及所有 ErrorViewResolver
,来构造 BasicErrorController
并注册到容器中。
ErrorAttributes
一般就是自动配置类中配好的 DefaultErrorAttributes
。
另外,一般情况下,我们也不会自定义 ErrorViewResolver
来做异常处理的定制化,所以 ErrorViewResolver
一般就只有一个,且就是自动配置类中配好的 DefaultErrorViewResolver
。
SpringBoot 各式各样的异常处理,基本都是会发送 /error
请求,通过 BasicErrorController
来完成错误响应。
1 2 3
| @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController {}
|
@RequestMapping
中的 ${server.error.path:${error.path:/error}}
是 SpEL 表达式,表达式计算过程是:先看 ${server.error.path}
是否有值,如果没有,再看 ${error.path}
是否有值,如果再没有,使用 /error
作为错误请求路径。
/error
请求,主要通过下面的两个处理器进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }
@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); }
|
浏览器访问页面时,请求处理出错,此时会调用 errorHtml
处理器响应错误页面。
使用 Postman 发送页面请求时,请求处理出错,此时会调用 error
处理器响应错误数据。
这种不同的处理器调用是如何实现的?
关键就在于 errorHtml
处理器上方的 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
。
如果 @RequestMapping
的 produces
属性提供了值,那么内容协商过程将会提前到请求映射阶段的查找处理器时就进行:SpringBoot 会从请求中获取客户端期望接收的媒体类型,检查是否和该 @RequestMapping
的 produces
属性指定的媒体类型兼容,如果兼容,且其他条件均匹配,才会将该 @RequestMapping
对应的处理器添加到成功匹配列表中。
下面结合源码举例说明。
假设浏览器或 Postman 发送请求 /basic_table
,请求处理会出错,默认将调用 response.sendError
将请求转发到 /error
。因为是请求转发,所以是同一个请求。
/error
的请求映射处理将经过以下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); addMatchingMappings(directPathMatches, matches, request);
if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { throw new IllegalStateException(); } } handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } }
|
DefaultErrorAttributes
默认错误属性
某一请求处理过程中发生异常后,异常请求一般会转发到 /error
,通过 BasicErrorController
响应错误页面或者响应错误数据。
响应的错误页面上或错误数据中,具体包含哪些错误信息,就是 DefaultErrorAttributes
来规定的。
一般情况下,我们不会自定义 ErrorAttributes
,所以下面自动配置会执行:
1 2 3 4 5
| @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
|
当异常请求转发到 /error
后,就会调用 BasicErrorController
的两个处理器 error
和 errorHtml
的其中一个进行错误响应处理。
处理过程中会调用注入到 BasicErrorController
内部的 DefaultErrorAttributes
的 getErrorAttributes
方法来获取要响应的错误信息。
1
| public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver {}
|
getErrorAttributes
其实是 ErrorAttributes
接口的方法。DefaultErrorAttributes
实现了该接口,我们来看下它对 getErrorAttributes
的具体实现,看看默认到底规定了要响应哪些错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (Boolean.TRUE.equals(this.includeException)) { options = options.including(Include.EXCEPTION); } 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.put("message", ""); } if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } return errorAttributes; }
@Override @Deprecated public 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; }
|
回顾我们的错误白页,其中包含的时间戳,状态码,以及其他错误信息,都是 DefaultErrorAttributes
规定好并返回的。
我们再来审视 getErrorAttributes
方法,最终响应的错误信息还包括了异常的相关信息,但是 getErrorAttributes
的形参列表中,我们只是传入了请求和一个需要的错误信息种类,并没有传入异常,那 getErrorAttributes
是怎么拿到异常相关的信息的呢,如异常类型,异常堆栈等?
答案就包含在 addErrorDetails
方法中,其实也不难想到,是从传入的请求的请求域中拿到异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) { Throwable error = getError(webRequest); if (error != null) { errorAttributes.put("exception", error.getClass().getName()); if (includeStackTrace) { addStackTrace(errorAttributes, error); } } addErrorMessage(errorAttributes, webRequest, error); }
@Override public Throwable getError(WebRequest webRequest) { Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE); return (exception != null) ? exception : getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION); }
|
这里明确一点,当前请求是 /error
请求,且这个请求是从前一个异常请求转发过来的。
所以不难想到,以 ERROR_ATTRIBUTE
为键,将前一个请求发生的异常放入请求域中的操作,肯定是在前一个异常请求转发到 /error
前完成的。具体是什么时候呢?
我们回到 DispatchServlet
的 doDispatch
方法,这次我们重点关注请求异常情况下的处理流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; try { ModelAndView mv = null; Exception dispatchException = null;
try { mappedHandler = getHandler(processedRequest); HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } }
|
假设本轮请求是 /basic_table
,执行目标方法时发生了异常,异常抛出后被保存到 dispatchException
,此时 mv
为 null
。
接下来,我们深入 processDispatchResult
函数,看异常是如何处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
if (exception != null) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); }
if (mv != null && !mv.wasCleared()) { render(mv, request, response); }
if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
|
接下来,继续深入看 processHandlerException
函数是怎么处理异常,并返回 ModelAndView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
ModelAndView exMv = null; if (this.handlerExceptionResolvers != null) { for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { exMv = resolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } } if (exMv != null) { return exMv; }
throw ex; }
|
我们看到了处理器异常解析器 HandlerExceptionResolver
的影子。
回顾 DefaultErrorAttributes
的类定义,可以发现,其实它实现了 HandlerExceptionResolver
接口。也就是说 DefaultErrorAttributes
是一个处理器异常解析器!
1
| public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver {}
|
DispatchServlet
中的 handlerExceptionResolvers
属性是一个 List<HandlerExceptionResolver>
,默认情况下,列表中第一个处理器异常解析器就是 DefaultErrorAttributes
。
我们来看 DefaultErrorAttributes
的 resolveException
实现:
1 2 3 4 5 6 7 8 9 10 11
| @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { storeErrorAttributes(request, ex); return null; }
private void storeErrorAttributes(HttpServletRequest request, Exception ex) { request.setAttribute(ERROR_ATTRIBUTE, ex); }
|
可以看到,DefaultErrorAttributes
的异常解析就是把异常 ex
放到了请求域中,然后就直接返回一个值为 null
。
至此,我们终于确定了,处理 /error
时从请求域中取出的异常,其实是前一个异常请求处理流程中,调用默认的处理器异常解析器 DefaultErrorAttributes
进行异常解析时放入的。
DefaultErrorAttributes
的 resolveException
总是返回 null
,这说明它 存在的意义其实并不是为了解析异常,而是将异常放入请求域,方便转发到 /error
后还可以拿到前一个异常请求发生的异常。
最后简单介绍一下处理器异常解析器。
处理器异常解析器的 resolveException
工作是,对处理器调用执行过程中产生的异常进行处理,然后返回一个 ModelAndView
对象。
resolveException
方法常见的处理一般有两种:
返回一个包含错误响应视图的 ModelAndView
。
这种情况下,异常请求后续将直接渲染 ModelAndView
中的错误响应视图,不会再请求转发到 /error
,再经过 BasicErrorController
来完成错误/异常响应。
手工调用 response.sendError(status, message)
,最后返回一个 ModelAndView
壳对象,至少视图为空。
这种情况下,异常请求后续会请求转发到 /error
,交给BasicErrorController
来完成错误/异常响应。
response.sendError()
并不立即停止当前请求的处理,它只是设置了一个标志,告诉 Servlet 容器这个请求已经结束,然后 Servlet 容器将会在适当的时机转发到错误页面,Tomcat 默认是转发到 /error
。
DefaultErrorViewResolver
默认错误视图解析器
一般情况下,我们不会自定义 ErrorViewResolver
,所以下面自动配置会执行:
1 2 3 4 5 6
| @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); }
|
来看 DefaultErrorViewResolver
的类定义:
1
| public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {}
|
DefaultErrorViewResolver
实现了 ErrorViewResolver
接口,是一个错误视图解析器。它提供了一种默认的错误视图解析策略:根据错误状态码进行错误视图解析,返回错误状态码对应的错误视图。
回过头来看 BasicErrorController
中的 errorHtml
处理器:
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }
|
errorHtml
处理器方法中的 resolveErrorView
方法从命名上看是解析错误视图,应该和 DefaultErrorViewResolver
有关,深入看下其源码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
|
果然,在 resolveErrorView
方法内部,我们看到了 ErrorViewResolver
的影子。
BasicErrorController
中的 errorViewResolvers
属性是一个 List<ErrorViewResolver>
,前面我们提到过,该属性是 BasicErrorController
在自动配置时,获取 Spring 容器中所有的 ErrorViewResolver
实现类来进行赋值的。
默认情况下,Spring 容器中只存在 DefaultErrorViewResolver
一个 ErrorViewResolver
的实现类,且我们一般也不自定义 ErrorViewResolver
。
所以 BasicErrorController
的 errorViewResolvers
集合中,默认只包含一个错误视图解析器,且就是 DefaultErrorViewResolver
。
下面来看 DefaultErrorViewResolver
的 resolveErrorView
具体实现,看看它是怎么进行错误视图解析的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<Series, String> SERIES_VIEWS;
static { Map<Series, String> views = new EnumMap<>(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())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } }
|
具体的解析逻辑在 resolve
方法中,我们继续看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); }
private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }
|
StaticView
默认静态错误视图 + BeanNameViewResolver
Bean 命名视图解析器
还是回过头来看 BasicErrorController
中的 errorHtml
处理器方法:
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }
|
可以看到,其实 StaticView
和 BeanNameViewResolver
是配套使用,来为 DefaultErrorViewResolver
处理不了的情况进行兜底的,保证最后还是能响应出一个错误页面,这个错误页面渲染出来就是最开始提到的白页。
DefaultErrorViewResolver
如果解析成功,假设返回的 ModelAndView
中的 view
就是 error/404
,那么使用 Thymeleaf
视图模板技术的情况下,视图解析环节就会选中使用 ThymeleafViewResolver
作为视图解析器来解析,得到对应 templates/404.html
的 ThymeleafView
对象。
DefaultErrorViewResolver
如果解析失败,返回的 ModelAndView
中的 view
就是 error
,视图解析环节就会选中使用 BeanNameViewResolver
作为视图解析器来解析,从 Spring 容器中获取 id 为 error
的 View
对象,发现 StaticView
满足要求,就将其返回。
可以来简单看一下 StaticView
的渲染逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (response.isCommitted()) { String message = getMessage(model); logger.error(message); return; } response.setContentType(TEXT_HTML_UTF8.toString()); StringBuilder builder = new StringBuilder(); Object timestamp = model.get("timestamp"); Object message = model.get("message"); Object trace = model.get("trace"); if (response.getContentType() == null) { response.setContentType(getContentType()); } builder.append("<html><body><h1>Whitelabel Error Page</h1>").append( "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>") .append("<div id='created'>").append(timestamp).append("</div>") .append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error"))) .append(", status=").append(htmlEscape(model.get("status"))).append(").</div>"); if (message != null) { builder.append("<div>").append(htmlEscape(message)).append("</div>"); } if (trace != null) { builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>"); } builder.append("</body></html>"); response.getWriter().append(builder.toString()); }
|
定制异常处理逻辑
上面我们已经说过一种异常处理定制,借助 DefaultErrorViewResolver
来实现自定义错误页:
- 如果
templates
或静态路径下有精确的错误状态码页面,如 error/404.html
,就显示精确的错误状态码页面
- 否则,如果
templates
或静态路径下有模糊的错误状态码页面,如 error/4xx.html
,就显示模糊的错误状态码页面
都没有的话,自动就是显示错误白页了。
SpringBoot 支持各式各样的异常处理定制,比如:
- 你可以自己向容器中注入一个 id 为
error
的 View
对象,来替代兜底的错误白页,让渲染出来的页面更好看。
- 你可以自定义
ErrorAttributes
,或者修改 DefaultErrorAttributes
,让它支持输出更多种类的错误信息。
- 你可以自定义
ErrorViewResolver
,或者修改 DefaultErrorViewResolver
,让它可以额外支持 3xx
的模糊状态码。
- 你甚至可以自定义
ErrorController
,或者修改 BasicErrorController
的默认行为。
但一般来说,不太建议自定义 ErrorAttributes
,ErrorViewResolver
以及 ErrorController
。
SpringBoot 异常处理定制里面,更推荐更常用的定制点是 HandlerExceptionResolver
。
上面讲解 DefaultErrorAttributes
时,我们提到了处理器异常解析器。我们提到 DispatchServlet
中有一个 List<HandlerExceptionResolver> handlerExceptionResolvers
属性,默认情况下该属性列表中第一个处理器异常解析器就是 DefaultErrorAttributes
。
我们都知道 DefaultErrorAttributes
本身不做异常解析,那么除它之外,handlerExceptionResolvers
中默认情况下还有哪些处理器异常解析器呢?
默认情况下,handlerExceptionResolvers
中包含两个处理器异常解析器,第一个是 DefaultErrorAttributes
,第二个是 HandlerExceptionResolverComposite
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
@Nullable private List<HandlerExceptionResolver> resolvers;
@Override @Nullable public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) { for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) { ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); if (mav != null) { return mav; } } } return null; }
}
|
HandlerExceptionResolverComposite
见名知义,其实它是一个处理器异常解析器的组合,它内部的 resolvers
属性默认还包括三个处理器异常解析器:
ExceptionHandlerExceptionResolver
:支持 @ControllerAdvice
+ @ExceptionHandler
处理全局异常
ResponseStatusExceptionResolver
:支持 @ResponseStatus
处理自定义异常
DefaultHandlerExceptionResolver
:处理 Spring 框架底层异常
前两个处理器异常解析器所支持的两种机制,就是 SpringBoot 异常处理为我们预留的扩展点/定制点,其中 @ControllerAdvice
+ @ExceptionHandler
处理全局异常的机制是最常用的。
另外,我们也可以自定义处理器异常解析器来进行异常处理定制:比如自定义实现 HandlerExceptionResolver
接口,然后将实现类以 @Component
方式注册到容器中,DispatchServlet
自动配置时,会将 Spring 容器中的所有 HandlerExceptionResolver
实现收集注入到内部属性 handlerExceptionResolvers
中。
以下三个定制点具体如何定制,这里就不做赘述了,雷丰阳老师的视频已经讲得挺清楚了:
@ControllerAdvice
+ @ExceptionHandler
处理全局异常
@ResponseStatus
处理自定义异常
- 自定义处理器异常解析器
详情可以看:55、错误处理-【源码流程】几种异常处理原理