关于springboot的异常处理以及源码分析(一)

一、什么是异常处理

1、文档定义

首先我们先来看springboot官方对于异常处理的定义。springboot异常处理
在文档的描述中,我们首先可以看到的一个介绍如下:

By default, Spring Boot provides an /error mapping that handles all errors in a sensible way, 
and it is registered as a “global” error page in the servlet container. For machine clients, 
it produces a JSON response with details of the error, the HTTP status, and the exception message.For browser clients, there is a “whitelabel” error view that renders the same data in HTML format (to customize it, add a View that resolves to error). To replace the default behavior completely, you can implement ErrorController and register a bean definition of that type or add a bean of type ErrorAttributes to use the existing mechanism but replace the contents.
默认情况下,Spring Boot提供了一个/error映射,以合理的方式处理所有错误,并且它在servlet容器中注册为“全局”错误页面。
对于机器客户端,它生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“白标签”错误视图,在中呈现相同的数据HTML格式(要自定义它,请添加一个解析为错误的视图)。
要完全替换默认行为,可以实现ErrorController并注册该类型的bean定义,或添加ErrorAttributes类型的bean以供使用
现有的机制,但取代了内容。

我们看到这里描述的是,当我们发生错误的时候,他默认提供了一个/error的映射(其实就是一个controller方法),他会给你转到这个映射上面,然后返回不同的视图。其中对于机器客户端请求(比如postman这种)就会返回一个json的响应,自然是包含了你的异常信息的。如果对于浏览器客户端的请求,就会返回一个空白的异常页面,在浏览器端渲染出来。
而且你也可以替换默认行为,自己实现ErrorController。这里我们先不说自定义,我们先来看看,默认行为是不是真的是这样的。

@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/testError")public String testError() {int a = 1 / 0;return "error";}
}

我们声明了一个TestController ,里面有一个get请求,故意制造了一个错误,很经典的错误1/0。
我们分别用postman和浏览器来请求测试一下。

  • postman模拟机器客户端
    在这里插入图片描述

  • 浏览器模拟浏览器客户端
    在这里插入图片描述
    所以我们看到默认情况是没问题的。

2、定制异常返回页面

我们再来看文档的下面一部分。
在这里插入图片描述
我们看到这里说的是,你要是觉得那种白页太丑了,确实也太丑了,啥也没有。我们可以自己定制页面。定制页面的方式也很简单,就是在静态资源目录下面放一个目录error,然后目录下面放404的html用来返回404请求,可以放一个5xx的页面用来返回异常的页面,OK,我们就来试试。
我的结构如下:
在这里插入图片描述
我自己的页面其实就是显示一个一级标题,404 5XX这样。我们来试试。
在这里插入图片描述
在这里插入图片描述
我们看到没毛病,完全OK。

二、源码分析

1、组件功能

我们先来看一下源码,而源码的整体流程实现基于这些组件的能力串联起来,最后形成了一个处理流程。
在springboot中我们没有处理过异常,他就给我们提供给了这些能力,那一定是自动装配机制提供的。那我们就去autoconfigure这个包下面去找。
而这个功能其实是web开发才有的,于是就在自动装配的web包下面看看。
最终我们找到一个很像error包:org/springframework/boot/autoconfigure/web/servlet/error
在这里插入图片描述
我们看到这个包下面有一个ErrorMvcAutoConfiguration的类,这个一看就是自动注入的核心类,springboot底层各种AutoConfiguration结尾的类都是做自动装配能力的。
于是我们点进去看看,我们看到他注入了很多组件,下面我们一一来分析一下。
鉴于理解顺序,我会从源码位置的从下到上来分析,但是都是在这个类里面的。
而且这个自动配置类有一些生效条件如下。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
// 属性绑定,你可以在配置文件配置这些内容来替代默认值
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })

组件0:DefaultErrorAttributes视图里面有哪些内容

我们之前在页面看到过,视图返回的不管是浏览器看到的异常白页还是postman看到的异常json,都会有一些属性封装。
在这里插入图片描述
我们看到有时间,有异常信息,状态码500等等。这个组件就是决定了有哪些内容的,我们来看下。

@Bean
// 默认的异常处理,如果用户没有配置,就使用这个,你可以自己配置一个ErrorAttributes注入来替换他这个
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {return new DefaultErrorAttributes();
}

但是我们还是要进去看看,他到底干嘛的。点进去我就后悔了,太TM长了,我们就从这个方法可以看到,他其实就是组装了一个map,里面确定了你能放的属性,也就是最后返回视图的内容。注意这里他组装了一个map,里面放着我们那些异常信息。

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;
}

组件1:StaticView 静态视图

这是一个名为静态视图的类,他实现了springmvc中的视图接口view。
其中的render为该视图长啥样的渲染实现,我们就主要来看看这个render方法。注意这个render方法需要一个map为他的静态视图添加异常信息。

private static class StaticView implements View {// http返回类型为html这种页面类型
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response){// 设置response返回类型为页面类型,因为视图渲染的就是页面response.setContentType(TEXT_HTML_UTF8.toString());// html内容的字符串拼接StringBuilder builder = new StringBuilder();// 取出当前时间,这个取出来的就是我们组件0放进去的,来这里拼接页面Object timestamp = model.get("timestamp");// 取出异常信息Object message = model.get("message");// 取出异常堆栈Object trace = model.get("trace");if (response.getContentType() == null) {response.setContentType(getContentType());}// 下面拼接的就是那个白页,中间可能通过htmlEscape()方法去除了一些标签之类的,用map中的异常信息填补异常页的信息。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());
}

所以我们这里得到第一个组件,就是这里渲染了一个页面视图。

组件2:WhitelabelErrorViewConfiguration 白页组装

在第一个组件有了之后,我们要在这个组件里面定义一套组件,来实现白页的组装。这是个静态内部类,里面注入了一系列组件来完成这件事。

// 开启lite模式
@Configuration(proxyBeanMethods = false)
/**生效条件:当你在配置文件中配置了server.error.whitelabel以下配置才会生效。但是如果你没配置,matchIfMissing = true也会决定你依然生效,其实就是他自己有默认值,你就是不配人家也有个值,也能生效,但是你配置了,就按你的来了。
*/
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {// 这里定义了一个我们的组件1,其实就是准备好那个白页了private final StaticView defaultErrorView = new StaticView();/*** 组件2.1:定义白页视图* 容器中还会放置一个名字叫error的视图,这个视图生效的条件是当容器中没有叫做error的视图的时候* 源码这个就会生效,换言之,你可以自己定义一个来替换掉他的这个。*/@Bean(name = "error")@ConditionalOnMissingBean(name = "error")public View defaultErrorView() {// 这个视图返回的其实就是我们的组件1return this.defaultErrorView;}/*** 组件2.2:视图解析器* 容器中放一个视图解析器,这个视图解析器是BeanNameViewResolver,可以通过视图的名字来解析视图* 这个就是和上面这个defaultErrorView配合工作的,他按照名字error查找到这个视图,然后渲染出来* 返回,所以我们可以来替代这个视图,我们可以自己定义一个名字叫做error的视图。而他的主要实现* 代码如下:* return context.getBean(viewName, View.class);就是简单的传入一个视图名字,然后他从容器* 中去取出来而已,其实就是封装了一个方法,用来从容器里面取我们注入进去的视图的。你说巧了不是* 我们的组件2.1刚在容器里面放了一个白页的视图,这里其实就是用来取白页视图用的。配套方法而已。*/@Bean@ConditionalOnMissingBeanpublic BeanNameViewResolver beanNameViewResolver() {BeanNameViewResolver resolver = new BeanNameViewResolver();resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);return resolver;}
}

组件3:BasicErrorController 默认跳转

我们前面看文档的时候看到,当异常发生,他会跳转去一个controller的error请求,来转发异常是给机器客户端的json还是浏览器客户端的白页,这个就是干这个功能的。

/**生效条件,当不存在ErrorController的时候就用这个,要是你自己定义了,就用你的,所以这里也是扩展点以后我们可以自己定义来取代他这个。
*/
@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得实现如下:

/*** 这是一个controller,所以这个controller和我们写的没啥区别,我们看到他的请求路径是这样的*${server.error.path:${error.path:/error}} 表达式的意思是:首先尝试解析 server.error.path 属性。如果该属性未定义,* 则使用 error.path 属性。如果 error.path 也未定义,则使用默认路径 /error。所以我们这里就可以知道,* 他的异常处理默认请求的controller大路径是/error,当然我们也可以通过配置文件来修改这个默认的请求路径。你改了就用你的了*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {private final ErrorProperties errorProperties;// 省略没用的....../*** 我们看到这里是异常html的处理,所以当你请求的异常是在页面的时候produces = MediaType.TEXT_HTML_VALUE* 此时就会进入这个方法,然后返回一个ModelAndView对象,这个对象里面包含了错误信息,并且给你跳转去* 错误页面,所以这个方法就是处理异常的。浏览器请求接口的异常来这里,然后通过ModelAndView跳去异常视图*/@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)// "text/html"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());// 构建异常视图,给前端返回,这里就用到了我们的组件2.2,他取到了error视图,返回到这里ModelAndView modelAndView = resolveErrorView(request, response, status, model);// 页面响应响应error这个视图,其实就是我们的白页视图return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);}/*** 非页面的响应,在这里处理,直接返回json数据,比如我们用postman测试的时候,就会进入这个方法,不是给html页面响应* ResponseEntity返回类型就是字符串类型,其实就是个json*/@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity<>(status);}// 这里构建那个jsonMap<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));return new ResponseEntity<>(body, status);}
}

组件4:DefaultErrorViewResolverConfiguration默认的视图解析器

奇怪了,我们上面在组件2.2已经有了一个视图解析器了,为啥这里又来一个,不知道你有没有印象,我们2.2组件是解析的默认的白页。但是我们还有一个场景是我们自己定制了异常页面,就是我们的400.html和5xx.html。然后他就生效了,所以这个解析器,是为了我们自己定制那个场景生效的。

@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {private final ApplicationContext applicationContext;private final ResourceProperties resourceProperties;// 省略构造....../*** 注入bean* @ConditionalOnBean(DispatcherServlet.class):当你是DispatcherServlet才生效,* 其实就是web环境。我们这分析的就是web,你说尼玛呢。* * @ConditionalOnMissingBean(ErrorViewResolver.class):当容器中没有ErrorViewResolver* 的时候他生效,所以你依然可以自定义代替他。*/@Bean@ConditionalOnBean(DispatcherServlet.class)@ConditionalOnMissingBean(ErrorViewResolver.class)DefaultErrorViewResolver conventionErrorViewResolver() {return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);}}

我们再来看看这个默认视图解析器的能力。

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {private static final Map<Series, String> SERIES_VIEWS;/**这里初始化了静态map,放了一个映射,我们看到其实放的就是异常码CLIENT_ERROR=4->4xx, 代表4xx异常,比如404SERVER_ERROR=5->5xx; 代表5xx异常,比如500*/ 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);}private ApplicationContext applicationContext;private final ResourceProperties resourceProperties;private final TemplateAvailabilityProviders templateAvailabilityProviders;private int order = Ordered.LOWEST_PRECEDENCE;// 省略构造函数@Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);// 根据异常的code来获得对应的视图if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);}return modelAndView;}/**根据异常的code来返回一个视图,viewName就是4xx还是5xx*/private ModelAndView resolve(String viewName, Map<String, Object> model) {// 视图的名字进一步拼接,我们看到他是去静态资源目录下获取error目录下的视图的。是不是对上了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);// 取出我们的4xx或者5xx页面resource = resource.createRelative(viewName + ".html");if (resource.exists()) {// 如果取到了,就返回我们自己定制的视图return new ModelAndView(new HtmlResourceView(resource), model);}}catch (Exception ex) {}}return null;}// 省略不是核心的代码......
}

OK,至此,我们一共六个组件就全部登场了,而springboot的异常处理流程也就是在这六个组件的配合下完成的,下面我们就来看看他们是怎么合作来完成的这个功能。

2、异常处理流程

OK,我们来操作一下关于异常处理流程,我们首先老套路,所有的操作都位于org.springframework.web.servlet.DispatcherServlet#doDispatch
然后既然他是在我们方法执行之后的异常处理,那么我们就先找到方法执行。
在这里插入图片描述
看注释你也知道,这行代码就是真正的目标方法执行,我们把断点打在这里。然后发起请求。
在这里插入图片描述
然后我们在浏览器发出请求。
在这里插入图片描述
不出意外,我们看到了异常抛出,并且随后在catch中捕获,把异常保存在了一个变量里面。因为是处理异常的,所以这里就拿到了异常。

dispatchException = ex;

紧接着往下走来到了这行代码:

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

看名字也知道是处理结果的,而且,他传入了几个参数,分别是:
1、方法执行返回的结果。
2、response。
3、mappedHandler是谁处理的,哪个handler。
4、mv,也就是处理的返回结果视图。
然后我们进入这个方法。他的实现如下。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 是否处理过了,就是个处理过没处理过的标识boolean errorView = false;// 异常是不是空,我们来到这里肯定不是空,因为已经抛出了除数为0的异常了,所以肯定会进来if (exception != null) {// 异常类型是不是ModelAndViewDefiningException,我们没定义过,所以不是这个if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}// 于是来到这里else {// 判断mappedHandler 是不是空,其实就是谁处理了我们这个方法,// 不为空,获取到Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);/**这里是真正处理我们的异常的地方,我们来看这个方法,这个方法经过解析器之后,什么也没干,就把异常抛出去了。**/mv = processHandlerException(request, response, handler, exception);// 所以这里必然mv这个视图没被渲染,他还是空的,errorView = (mv != null);}}......省略没用的
}

我们这里分析一下,processHandlerException()来看这个真正处理异常的地方

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// ......省略没用的// 这里开始声明了一个视图,其实就是我们要返回的视图ModelAndView exMv = null;// 遍历所有的异常解析器,看那个一个能处理我们的异常,就交给哪个处理,根据下面的截图我们可以看到,这个视图解析器集合里面一共有四个解析器。/**1、DefaultErrorAttributes,如果你眼熟的话,其实可以看到,这就是我们上一小章看到的组件0我们跟着这个组件0进去看看他做啥了。其实就是在request域里面放了一下这个异常request.setAttribute(ERROR_ATTRIBUTE, ex);然后返回了一个空视图。下面还有三个解析器,很遗憾的告诉大家,这三个解析器都不符合解析要求,所以他们其实啥也没干。2、ExceptionHandlerExceptionResolver3、ResponseStatusExceptionResolver4、DefaultHandlerExceptionResolver*/if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}// 经过上面的解析我们得知其实只有我们的组件0生效了,但是返回了一个空的视图// 所以下面的都不会走,直接走到最后一步if (exMv != null) {if (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}// We might still need view name translation for a plain error model...if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}if (logger.isTraceEnabled()) {logger.trace("Using resolved error view: " + exMv, ex);}else if (logger.isDebugEnabled()) {logger.debug("Using resolved error view: " + exMv);}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}// 我们看到,他什么都不会做,只会在这里把这个异常原模原样的抛出throw ex;
}

所有的异常解析器
我们看到上面,在经过一系列异常解析器之后,他并没有一个解析器能处理它,他在最后就抛出了一个异常。当前请求就结束了,啊?你结束了,?那我那一堆组件都白给了?不会的,我们放行这一步请求就会看到一个现象。
在这里插入图片描述
他再次来到了入口处的org.springframework.web.servlet.DispatcherServlet#doDispatch这个方法,而且这次的请求路径是/error,这其实是servlet的规范,在无法处理异常之后,会抛出异常,再次发起一次请求,而请求的路径就是/error,不知道你有没有想起来,我们的组件3就是一个controller,并且他处理的请求路径,就是/error。
然后再次经过org.springframework.web.servlet.DispatcherServlet#doDispatch的派发,会得知我们这个controller可以处理这个/error

注意这个/error也是一次请求,所以也要走之前请求的路程,包括派发,拦截器等等。最终来到BasicErrorController 。

在这里插入图片描述
于是,我们这个第二次请求就会来到这个controller里面被处理。org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {private final ErrorProperties errorProperties;// 省略没用的......// 当请求打到/error这里的时候,会根据请求来的类型是html的还是postman这种类型的,走入不同的接口,因为我这次是页面请求的,所以我以这个方法为例。/*** 我们看到这里是异常html的处理,所以当你请求的异常是在页面的时候produces = MediaType.TEXT_HTML_VALUE* 此时就会进入这个方法,然后返回一个ModelAndView对象,这个对象里面包含了错误信息,并且给你跳转去* 错误页面,所以这个方法就是处理异常的。浏览器请求接口的异常来这里,然后通过ModelAndView跳去异常视图*/@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)// "text/html"public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {// 因为前面在当前request域中封装了异常,所以这里可以通过当前request获取到我们的异常// 包括异常的code和信息,封装在HttpStatus HttpStatus status = getStatus(request);/**这里是获取我们的DefaultErrorAttributes也就是组件0,来获取他里面能放的异常属性,然后扔到一个map里面。注意这个map,我们前面说过,空白页的异常新秀填补需要一个map,而这个map就是在这里弄出来的。*/Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));response.setStatus(status.value());// 构建异常视图,给前端返回/**解析异常视图,他会去通过我们的组件4,去静态目录下面获取是不是有我们的异常code对应的html,如果找到了,就包装为视图返回。*/ModelAndView modelAndView = resolveErrorView(request, response, status, model);// 页面响应响应error这个页面/**这里存在两个逻辑。1、我们的组件4解析的视图是不是为空,如果不是空,那就返回我们组件4解析的视图,也就是我们自己定义的那些4xx 5xx。2、如果为空,那么就返回一个new ModelAndView("error", model),返回了一个叫做error的ModelAndView。而同时把这个拥有异常信息的map放进去了。*/return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);}/*** 非页面的响应,在这里处理,直接返回json数据,比如我们用postman测试的时候,就会进入这个方法,不是给html页面响应* ResponseEntity返回类型就是字符串类型,其实就是个json*/@RequestMappingpublic 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);}@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {HttpStatus status = getStatus(request);return ResponseEntity.status(status).build();}protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {ErrorAttributeOptions options = ErrorAttributeOptions.defaults();if (this.errorProperties.isIncludeException()) {options = options.including(Include.EXCEPTION);}if (isIncludeStackTrace(request, mediaType)) {options = options.including(Include.STACK_TRACE);}if (isIncludeMessage(request, mediaType)) {options = options.including(Include.MESSAGE);}if (isIncludeBindingErrors(request, mediaType)) {options = options.including(Include.BINDING_ERRORS);}return options;}// 省略没用的......
}

所以到这里我们这个error的请求也就在doDispatch的

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行完了,我们接着往下看会看到这么一行代码。

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

这里就是error最后走到这里处理他的视图。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 这次就不是异常了,所以这里不走if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// 处理视图解析,这里就开始了,if (mv != null && !mv.wasCleared()) {/**最终会来到这里org.springframework.web.servlet.DispatcherServlet#resolveViewName这里面我们的组件2.2会登场,在容器中找到名字叫做error的视图,也就是我们的组件1,并且用我们前面构造的拥有异常信息的map来填补这个视图。并且经过组件2.1之后,把我们前面在mv里面塞的那些异常都给到组件2.1此时就返回了我们的那个白页。于是这样就返回了,我们的东西。所以,他是早就注入了空白页视图,然后拿到异常装在map里面,后面通过空白页视图解析器从容器找到这个视图,把map中的异常信息塞进去,就返回了。*/render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}
}

我们看到这个过程,组件0-4依次登场完成最后的处理。
因为我没有用4xx 5xx定制,所以组件4其实没走他的渲染,其实原理是一样的。后面我会补一张图,并且给出开发中的一些异常的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/406970.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络-2-tcpip协议

1.说说 TCP/IP 四层模型&#xff1f; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;模型是一种用于描述互联网通信的协议层次结构。它分为四个主要层次&#xff0c;每个层次都定义了不同的协议来实现特定的功能。下面是TCP/IP模型各层的常用…

Android系统安全 — 1-OpenSSL支持的常用加解密算法介绍

常用加解密算法介绍 1. 哈希算法 常见的函数包含MD系列、SHA-1、SHA-2家族、SHA-3家族、SM3等。 1.1 MD5&#xff08;单向散列算法&#xff09; 全称是Message-Digest Algorithm 5&#xff08;信息-摘要算法&#xff09;&#xff0c;经MD2、MD3和MD4发展而来。MD5算法的使用…

0基础学习Python路径(21)Python NameSpaceScope

命名空间定义了在某个作用域内变量名和绑定值之间的对应关系&#xff0c;命名空间是键值对的集合&#xff0c;变量名与值是一一对应关系。作用域定义了命名空间中的变量能够在多大范围内起作用。 命名空间在 Python 解释器中是以字典的形式存在的&#xff0c;是以一种可以看得…

【快速入门 LVGL】-- 1、STM32 工程移植 LVGL

目录 一、LVGL 简述 二、复制一个STM32工程 三、下载 LVGL 四、裁剪 源文件 五、工程添加 LVGL 文件 六、注册 显示 七、注册 触摸屏 八、LVGL 心跳、任务刷新 九、开跑 LVGL 十、控件的事件添加、响应处理 十 一、几个好玩小事情 十 二、显示中文 ~~ 约定 ~~ 在…

从【人工智能】到【计算机视觉】,【深度学习】引领的未来科技创新与变革

前几天偶然发现了一个超棒的人工智能学习网站&#xff0c;内容通俗易懂&#xff0c;讲解风趣幽默&#xff0c;简直让人欲罢不能。忍不住分享给大家&#xff0c;点击这里立刻跳转&#xff0c;开启你的AI学习之旅吧&#xff01; 前言 – 人工智能教程https://www.captainbed.cn/l…

linux文件——用户缓冲区——概念深度探索、IO模拟实现

前言&#xff1a;本篇文章主要讲解文件缓冲区。 讲解的方式是通过抛出问题&#xff0c; 然后通过分析问题&#xff0c; 将缓冲区的概念与原理一步一步地讲解。同时&#xff0c; 本节内容在最后一部分还会带友友们模拟实现一下c语言的printf&#xff0c; fprintf接口&#xff0c…

跨境电商补单秘籍:Lazada、Shopeee、eBay、Wish等平台实战技巧

在跨境电商领域&#xff0c;Lazada、Shopee、eBay、Wish及速卖通等平台为商家提供了广阔的市场空间。为了有效扩大产品的曝光率和提升转化率&#xff0c;商家需充分利用平台活动及营销工具。平台活动不仅是获取流量的关键渠道&#xff0c;还能显著提升品牌知名度。此外&#xf…

Python画笔案例-006 绘制正多边形

1、绘制正多边形 通过 python 的turtle 库绘制一个正多边形的图案&#xff0c;如下图&#xff1a; 2、实现代码 绘制一个正多边形&#xff0c;关键两个因素&#xff0c;一个是边长&#xff0c;决定了图形的大小&#xff1b;另一个就是图形里每个角的角度&#xff0c;绘制多边形…

SSM健康生活博客小程序—计算机毕业设计源码23497

摘 要 本文设计了一种基于SSM框架的健康生活博客小程序&#xff0c;为人们提供了运动视频教学、博客信息分享&#xff0c;用户能够方便快捷地查看资讯、搜索健康方面的相关信息、还能发布个人生活博客等。健康生活博客小程序采取面对对象的开发模式进行软件的开发和硬体的架设&…

Python | Leetcode Python题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution:def canMeasureWater(self, x: int, y: int, z: int) -> bool:if x y < z:return Falseif x 0 or y 0:return z 0 or x y zreturn z % math.gcd(x, y) 0

opencv-python图像增强十一:图像强光逆光调整:

文章目录 一&#xff0c;简介&#xff1a;二&#xff0c;方案简述&#xff1a;三&#xff0c;算法实现步骤&#xff1a;3.1 获得图像的阴影区域&#xff1a;3.2 调整阴影区域的亮度和对比度 四&#xff1a;整体代码五&#xff0c;效果&#xff1a; 一&#xff0c;简介&#xff…

UE5 多个类选择界面生成。解决方案思路。

中控器CC 》用户界面控制器UI_CC 》用户界面UI_Inst 生成 CC使用接口&#xff0c;通知UI_CC开始生成UI_Inst。 蓝图函数库编写判断是否存在和创建UI的蓝图。&#xff08;此处略&#xff09; UI_CC生成时&#xff0c;userwidget使用接口&#xff0c;注册UI_CC的用户控件的控件…

系统编程-信号

6 信号与管道 1 目录 6 信号与管道 1 信号 信号的概念 信号的使用 信号的发送 通过函数来实现信号的发送 信号改造函数(重点) 给自己发送信号函数 定时闹钟函数 暂停进程的函数 例题&#xff1a; 代码一&#xff1a; 代码二&#xff1a; 代码分析 -- linux系统下…

获发明专利加持,隆道加速推进企业级AI应用落地

近期&#xff0c;北京隆道网络科技有限公司研发的“基于供应链管理的AI采购业务分析装置及方法”获得国家发明专利授权。该项新专利的取得&#xff0c;证明了隆道在AI产业化应用中的技术前瞻性和创新性&#xff0c;也为隆道加速企业级AI应用落地提供了知识产权保障。 根据IBM发…

将标准输入stdin转换成命令行参数——Unix中的xargs指令

xargs是Unix中的复合指令加工机&#xff0c;联合管道符“|”将制造更加强大的“复杂”指令组合。 (笔记模板由python脚本于2024年08月22日 18:13:51创建&#xff0c;本篇笔记适合喜欢Linux的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.…

开放式耳机哪个品牌好?分享四款开放式蓝牙耳机排行榜前十名

我相信很多人都会有这些问题&#xff0c;不知道入手什么蓝牙耳机品牌、有线耳机不好收纳、有线耳机不方便携带、蓝牙耳机听歌的音质怎么样、蓝牙耳机是否会对大脑有危害、蓝牙耳机有什么品牌型号推荐以及想要不同价位的蓝牙耳机品牌推荐参考&#xff0c;okok问题也是很多&#…

GPU池化技术在油气勘探开发中的应用

01 背景介绍 国内某研究院为实现石油勘探开发专业软件资源的统一管理、统一监控、统一共享和统一计量&#xff0c;自主研发了勘探云管理平台(EPCP)和科研工作业务协同平台。该研究院通过两个平台实现了数十种专业勘探开发软件的共享&#xff0c;种类包括地震资料处理和解释&am…

汽车冷却液温度传感器

1、冷却液温度传感器的功能 发动机冷却液温度传感器&#xff0c;也称为ECT&#xff0c;是帮助保护发动机&#xff0c;提高发动机工作效率以及帮助发动机稳定运行的非常重要的传感器之一。 发动机冷却液温度 &#xff08;ECT&#xff09; 传感器用于测量发动机的冷却液温度&…

【Lecture1】清华大学大模型公开课——大模型绪论

#清华大模型公开课第二季 #OpenBMB 目录 1. The Evolution of Artificial Intelligence --History 人工智能的演变--历史 1.1 Definition of AI --定义 1.2 Conceptualization of AI -- 概念 1.3 Birth of AI as a Discipline 1.4 Development of AI 1.4.1 Symbolic Int…

有哪些好用的AI工具?这些AI工具让工作学习更高效!

大家好&#xff01;今天我要给大家介绍一些非常酷的AI工具&#xff0c;它们可以帮助你在不同领域发挥创意和提高效率。让我们一起来探索吧&#xff01; 我们有AI绘画工具。想象一下&#xff0c;你只需要描述一下你想要的画面&#xff0c;AI就能帮你生成各种风格的艺术作品。无论…