SpringBoot2核心技术-核心功能-Web开发-异常处理的全部流程
本文章是把SpringBoot收到请求异常后,如何进行异常的处理、跳转错误页的规则、定制化错误处理逻辑结合实例全部细讲了一次,其中还有SpringMVC的知识,预计字数5w+,预计阅读时间2小时,虽然很长,但是流程清晰,代码基本上都有批注,读完就能完全理解SpringBootd的异常处理的全部流程。
文章目录
因为SpringBoot2的源码太多而且比较复杂,为的是给自己和刚入门SpringBoot的小伙伴提供一个自己学习的笔记,比较粗略还望各位多多指教,本专栏文章根据自己看书和在尚硅谷学习SpringBoot2所做下,参考了一些它们的资料所写,如有侵权会删除。
其他相关文章:
倾情力作-一文读懂SpringBoot2源码-基础入门-自动配置原理
倾情力作-一文读懂SpringBoot2源码-web开发-静态资源访问原理
倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程
倾情力作-一文让你读懂SpringBoot2源码-web开发-页面渲染派发的全部流程
倾情力作-一文让你读懂SpringBoot2源码-Web开发-异常处理的全部流程
8.异常处理
8.1错误处理
8.1.1默认规则
1.默认情况下,Spring Boot提供/error
处理所有错误的映射,比如现在访问一个不存在的页面:
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。
对于浏览器客户端,响应一个"Whitelable"错误视图,以HTML格式呈现相同的数据.
2.要对其进行自定义,添加 View 解析为error
要完全替换默认行为,可以实现 ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。
error/下的4xx,5xx页面会被自动解析;
8.1.2异常处理自动配置原理
8.1.2.1ErrorMvcAutoConfiguration自动配置类
SpringBoot在底层为我们自动配置好了异常处理规则(ErrorMvcAutoConfiguration)
@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, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
}
那么在这个配置类(ErrorMvcAutoConfiguration)中配置了哪些组件呢?
8.1.2.1.1DefaultErrorAttributes
返回错误的一些属性,比如:
如果觉得错误页显示的这些值不够,可以自定义DefaultErrorAttributes显示更多的值
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
//==============================================DefaultErrorAttributes==========================================================
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
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;
}
@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;
}
}
8.1.2.1.2BasicErrorController
json或者是html白页的适配响应控制器
如果觉得页面的跳转逻辑需要自定义,可以自定义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==========================================================
@Controller
//处理默认/error路径的请求
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
/**
*浏览器响应的Html页面走这个映射
*/
@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);
//响应error页面
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));
//响应Map中的数据,相当于一个json数据
return new ResponseEntity<>(body, status);
}
}
8.1.2.1.3WhitelabelErrorViewConfiguration配置类
View->id:error(上面默认响应的error页面就是这个error页面)
BeanNameViewResolver->id:beanNameViewResolver(根据BeanName来找到视图的视图解析器)
StaticView->这个就是HTML默认响应出来的那个错误页面
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
//默认的ErrorView
private final StaticView defaultErrorView = new StaticView();
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
//====================================================StaticView================================================================
private static class StaticView implements View {
//响应一个HTML页面
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
//StaticView渲染视图的方法
@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());
}
//这里就是我们8.1.1对于机器客户端响应出来的那个页面
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());
}
}
8.1.2.1.4DefaultErrorViewResolver配置类
DefaultErrorViewResolver->id:conventionErrorViewResolver
如果不想把错误页面放在error文件夹下,可以自定义DefaultErrorViewResolver放在其他文件夹下
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ WebProperties.class, WebMvcProperties.class })
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final Resources resources;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, WebProperties webProperties) {
this.applicationContext = applicationContext;
this.resources = webProperties.getResources();
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
}
//===========================DefaultErrorViewResolver=======================================================
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
static {
//给Map中映射了一些东西
Map<Series, String> views = new EnumMap<>(Series.class);
//如果是客户端错误就是4xx
views.put(Series.CLIENT_ERROR, "4xx");
//如果是服务端错误就是5xx
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())) {
//调用下面的resolve()
//status.series()--->状态码比如404,500等
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
//返回一个ModelAndView对象
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//将404,500等状态码拼接为:error/404、error/500
//这个errorViewName相当于就找到了error目录下的自定义的404、500等错误信息的HTML页面
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider =this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
}
8.1.3异常处理的步骤流程
8.1.3.1案例
本次我们还是使用5.2.1的案例,案例在这里:
倾情力作-一文让你读懂SpringBoot2源码-web开发-页面渲染派发的全部流程
1.静态资源文件目录,下载链接: https://pan.baidu.com/s/1AGjKn8EbNkj0OqXEEHqiQg 提取码: 32zf
2.User类
@Data
public class User {
private String userName;
private String password;
}
3.Controller层
@Controller
public class TableController {
@GetMapping("/basic_table")
public String basic_table(){
//模拟异常
int i=10/0;
return "table/basic_table";
}
}
8.1.3.2异常处理的步骤流程
用8.1.3.1的案例
还是先到DispatcherServlet.doDispatch()
//SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//找到当前请求使用哪个Handler(Controller的方法)处理
//感兴趣的可以去看我上一篇博客(倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程)的3.1.2.2(本文开头目录有链接)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//找到当前请求对应的controller类型来调用相应的HandlerAdapter
//适配器执行目标方法并确定方法参数的每一个值,感兴趣的可以去看我上一篇博客(倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程)的3.2.2.1(本文开头目录有链接)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//调用AbstractHandlerMethodAdapter.handle()
//真正执行hanlder的方法,感兴趣的可以去看我上一篇(倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程)的3.2.2.2(本文开头目录有链接)
//在本次案例中,8.1.3.2.1走这里
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//就算返回的ModelAndView为空,也会设置一个默认的ViewName
applyDefaultViewName(processedRequest, mv);
//并且会跳转到默认的当前请求的映射路径(比如说本次案例没有返回值,那么会跳到默认的/login页面)
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
//执行目标方法,目标方法运行期间有任何异常都会被catch为dispatchException
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理派发结果,也就是去怎么渲染和响应去哪个页面
//在本次案例中,8.1.3.2.2走这里
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
8.1.3.2.1真正执行hanlder的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//====================mv = ha.handle(processedRequest, response, mappedHandler.getHandler());===============
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
//执行handler的方法是调用的RequestMappingHandlerAdapter. handleInternal()
return handleInternal(request, response, (HandlerMethod) handler);
}
//============================RequestMappingHandlerAdapter. handleInternal()================================
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 执行handler的目标方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
//=======================mav = invokeHandlerMethod(request, response, handlerMethod)========================
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//为handler的目标方法添加所有的参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//为handler的目标方法添加所有的返回值处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//创建一个ModelAndViewContainer
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//执行并处理handler的目标方法8.1.3.2.1.1看这里
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
//发出请求已完成的信号,8.1.3.2.1.2看这里
webRequest.requestCompleted();
}
}
8.1.3.2.1.1执行并处理handler的目标方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//===============================invocableMethod.invokeAndHandle(webRequest, mavContainer);=================
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//真正地执行handler目标方法,8.1.3.2.1.1.1走这里
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//处理handler的目标方法的返回值结果,本次流程不会走这一步
//有需要的可以看我上一篇博文(倾情力作-一文让你读懂SpringBoot2源码-web开发-页面渲染派发的全部流程)的3.2.2.2.1.1.3.2(在文章目录开头有链接)
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
8.1.3.2.1.1.1真正地执行handler目标方法
InvocableHandlerMethod.invokeForRequest(webRequest, mavContainer, providedArgs)
//===========InvocableHandlerMethod.invokeForRequest(webRequest, mavContainer, providedArgs)================
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取handler的所有请求参数值,本次我们暂时不细看这一步。
//有需要的可以看我上一篇博文(倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程)的3.2.2.2.1.1.3.1.1 (在文章目录开头有链接)
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//把获取到的参数放入doInvoke(args)真正的执行目标方法
//8.1.3.2.1.1.1.1走这里
return doInvoke(args);
}
8.1.3.2.1.1.1.1InvokableHandlerMethod.doInvoke(Object…args)
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
//执行目标方法-> public String basic_table(),8.1.3.2.1.1.1.1.1走这里
return method.invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(method, getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
//8.1.3.2.1.1.1.1.2抛出运行时异常
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
8.1.3.2.1.1.1.1.1method.invoke(getBean(), args);
调用执行到了目标方法
@GetMapping("/basic_table")
public String basic_table(){
int i=10/0;
return "table/basic_table";
}
8.1.3.2.1.1.1.1.2throw (RuntimeException) targetException
当我们执行到int i=10/0;时,会抛出这个异常
当异常抛出后我们会去到8.1.3.2.1.2
webRequest.requestCompleted()
8.1.3.2.1.2发出请求已完成的信号
public void requestCompleted() {
executeRequestDestructionCallbacks();
updateAccessedSessionAttributes();
//把requestActive从true设置为false
this.requestActive = false;
}
8.1.3.2.2处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
本次案例中因为中途的抛出异常
mv=null
dispatchException=
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//当异常不为空的时候,走这里
if (exception != null) {
//判断异常是否为ModelAndViewDefiningException,本次不是
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取到原生的handler(public String basic_table())
Object handler = (mappe dHandler != null ? mappedHandler.getHandler() : null);
//处理handler的异常,并且返回一个ModelAndView对象,8.1.3.2.2.1走这里
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
8.1.3.2.2.1处理handler的异常,并且返回一个ModelAndView对象
mv = processHandlerException(request, response, handler, exception)
//===================mv = processHandlerException(request, response, handler, exception)===================
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//遍历所有的HandlerExceptionResolver(处理器异常解析器),看哪个HandlerExceptionResolver能处理当前异常
//只有当我们自定义了异常解析器后才会使用这两个容器自带的HandlerExceptionResolver
//本次案例中没有任何HandlerExceptionResolver(处理器异常解析器)处理异常,所以还是把异常抛了出去
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
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;
}
在本次的案例中,我们还没有自定义错误异常处理方法,所以容器并没有为我们处理错误,在8.1.4.2定制错误处理逻辑的时候我们会自定义错误处理逻辑,来分析没有用到的下面三个异常解析器的原理和调用时机。
容器中自带的异常解析器
HandlerExceptionResolver
当我们自定义异常处理器的时候,只需要实现接口,重写resolveException()
8.1.3.2.3发送/error请求
根据以上的案例,很显然最后异常并没有被处理,当请求执行完成后,Servlet底层最终会再发送一个/error请求
这个请求最终会被8.1.2.1.2中的BasicErrorController处理
//=========BasicErrorController.errorHtml(HttpServletRequest request, HttpServletResponse response)=========
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//得到状态码500
HttpStatus status = getStatus(request);
//得到一些模型数据
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//解析错误视图8.1.3.2.3.1走这里
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//如果返回的modelAndView不为空,那么就返回当前的modelAndView
//否则就新建一个ModelAndView,viewName=error并且是StaticView(SpringBoot自动给我们默认配置的空白页错误页面)
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
8.1.3.2.3.1解析错误视图
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//============ ModelAndView modelAndView = resolveErrorView(request, response, status, model);=============
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,Map<String, Object> model)
{
//遍历所有的ErrorViewResolver(错误视图解析器)看谁能解析,系统默认只有一个8.1.2.1.4DefaultErrorViewResolver
for (ErrorViewResolver resolver : this.errorViewResolvers) {
//开始解析视图,返回ModelAndView,8.1.3.2.3.1.1走这里
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
8.1.3.2.3.1.1开始解析视图,返回ModelAndView
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
//====================================DefaultErrorViewResolver=============================================
//===========ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);=================
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//解析model返回ModelAndView,这里返回的是null
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
//如果ModelAndView==null并且SERIES_VIEWS和status.series()一样,那么就再次解析model
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//再次解析model,8.1.3.2.3.1.1.1走这里
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
//============ ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);==================
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//将404,500等状态码拼接为:error/404、error/500
//这个errorViewName相当于就找到了error目录下的自定义的404、500等错误信息的HTML页面
//本次案例第一次model解析的errorViewName=error/500
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider =this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//返回一个解析的资源(ModelAndView),这里返回的就是null
return resolveResource(errorViewName, model);
}
//====================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);
//最后页面会带上.html
//本次案例第一次到的第resource=error/500.html
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
//返回一个新的ModelAndView
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
//因为error文件夹在templates目录下,所以返回的是null
return null;
}
8.1.3.2.3.1.1.1再次解析model
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//本次案例第二次model解析的errorViewName=error/5xx
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.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
//本次案例第一次到的第resource=error/5xx.html,就匹配上了/templates/error/5xx.html
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
//返回了一个新的ModelAndView
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
后续就是拿到这个返回的ModelAndView再到处理派发结果,进行页面渲染最后的页面显示出来,自此我们就完成了异常处理的全部流程。
8.1.4定制错误处理逻辑
8.1.4.1自定义错误页
原理见8.1.3.2
error/404.html,error/500.html,有精确匹配的状态码页面就精确匹配,没有就找4xx.html,5xx.html,如果都没有就出发白页WhiteAbleErrorView.
8.1.4.2自定义异常解析器的处理步骤和执行原理
在8.1.3.2.2.1处理handler的异常的时候我们并没有使用到容器中给我们的那三个异常处理的异常解析器,而是通过servlet底层发送的"/error"请求去跳转到指定的错误页面,所以在这里我们会自定义错误处理逻辑,来分析没有用到的下面三个异常解析器的原理和调用时机。
容器中自带的异常解析器
8.1.4.2.1@ExeptionHandler+@ControllerAdvice
这两个注解处理的全局异常,底层就会被1-0的ExceptionHandlerExceptionResolver异常解析器解析到
使用的测试案例依旧是8.1.3.1案例
使用步骤:
1.新建一个类,在类上标注@ControllerAdvice
2.写一个方法来处理异常,在方法上标注 @ExceptionHandler(错误的类型)
3.返回视图地址或者是ModelAndView即可
/**
* 处理整个Web的Controller异常
* @author WxrStart
* @create 2022-04-14 21:18
*/
//@ControllerAdvice,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理数学运算异常和空指针异常
* @return
*/
@ExceptionHandler({ArithmeticException.class,NullPointerException.class})
public String handleArithmeticException(Exception e){
//返回视图地址或者是ModelAndView即可
return "login";
}
}
处理流程:
真正执行hanlder的方法在8.1.3.2.1详细讲过了,我们暂时忽略,我们之间进入处理派发结果的部分
1.处理派发结果
dispatchExcetion依旧是:
//==================processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)=====================
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//当异常不为空的时候,走这里
if (exception != null) {
//判断异常是否为ModelAndViewDefiningException,本次也不是
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取到原生的handler(public String basic_table())
Object handler = (mappe dHandler != null ? mappedHandler.getHandler() : null);
//处理handler的异常,并且返回一个ModelAndView对象,1.2走这里
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//当1.2完成后,就开始进行页面渲染,页面渲染的流程在5.2.2.2.1很详细了,这里就不作说明
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1.2处理handler的异常,并且返回一个ModelAndView对象
//===================mv = processHandlerException(request, response, handler, exception)===================
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//遍历所有的HandlerExceptionResolver(处理器异常解析器),看哪个HandlerExceptionResolver能处理当前异常
//只有当我们自定义了异常解析器或者是使用SpringMVC自带的异常处理器后才会使用这两个容器自带的HandlerExceptionResolver
//本次案例中我们自定义了ExeptionHandler,所以我们容器自带的HandlerExceptionResolver-HandlerExceptionResolverComposite异常解析器组
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//HandlerExceptionResolverComposite解析异常,1.3走这里
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
//多余代码省略
}
1.3HandlerExceptionResolverComposite解析异常
//================exMv = resolver.resolveException(request, response, handler, ex)==========================
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
//本次案例中我们自定义了ExeptionHandler,所以使用的是ExceptionHandlerExceptionResolver来解析异常
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
//===========AbstractHandlerExceptionResolver.resolveException(request, response, handler, ex)==============
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
//执行标注了@ExceptionHandler的目标方法
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
//=======================ExceptionHandlerExceptionResolver.doResolveException(request, response, handler, ex)===================
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
//找到抛出异常的Handler(这里是public String basic_table())
HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
//开始执行标注了@ExceptionHandler的目标方法
return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
//=========doResolveHandlerMethodException(request, response, handlerMethod, ex)============================
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
//设置所有的参数解析器
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//设置所有的返回值处理器
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
//新建一个ModelAndViewContainer用于存储视图和模型数据
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
// Expose causes as provided arguments as well
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
//开始解析参数,返回值,以及调用加了@ExceptionHandler的目标方法
//类似于一个正常的Handler调用流程,后面就是通过反射调用加了@ExceptionHandler的目标方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
catch (Throwable invocationEx) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
//将ModelAndViewContainer转化为ModelAndView
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
//返回转化后ModelAndView
return mav;
}
}
后续就是通过返回的ModelAndView进行页面渲染逻辑,跳转到login.html页面,页面渲染的流程在5.2.2.2.1很详细了,这里就不作说明了。
8.1.4.2.2@ResponseStatus+自定义异常
这个注解处理的全局异常,底层就会被1-1的ResponseStatusExceptionResolver异常解析器解析到
使用步骤:
1.新建一个异常类,在类上标注@ResponseStatus
2.在代码可能会出现异常的地方抛出异常
使用的测试案例依旧是8.1.3.1案例,但是Controller层为
@Controller
public class TableController {
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
List<User> users = Arrays.asList(
new User("zhangsan", "123456"),
new User("lisi", "654321"),
new User("wangwu", "abcdef"),
new User("zhaoliu", "fedcba")
);
//如果user数量大于3,则抛出自定义用户数量太多异常
if(users.size()>3){
throw new UserExcepetion();
}
model.addAttribute("users",users);
return "table/dynamic_table";
}
}
自定义异常类UserException
/**
* 自定义用户异常
* @author WxrStart
* @create 2022-04-15 8:44
*/
//这个异常可以返回状态码信息403,以及错误原因用户数量太多
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserExcepetion extends RuntimeException{
public UserExcepetion() {
}
public UserExcepetion(String message) {
super(message);
}
}
测试效果
处理流程:
真正执行hanlder的方法在8.1.3.2.1详细讲过了,我们暂时忽略,我们之间进入处理派发结果的部分
1.处理派发结果
dispatchException是:
//======================processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)================
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//当异常不为空的时候,走这里
if (exception != null) {
//判断异常是否为ModelAndViewDefiningException,本次也不是
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取到原生的handler(public String dynamic_table(Model model))
Object handler = (mappe dHandler != null ? mappedHandler.getHandler() : null);
//处理handler的异常,并且返回一个ModelAndView对象,1.2走这里
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//这次返回的ModelAndView是空的所以不走这里,走的是8.1.3.2.3的处理逻辑
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1.2处理handler的异常,并且返回一个ModelAndView对象
//===========================mv = processHandlerException(request, response, handler, exception)=============================
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//遍历所有的HandlerExceptionResolver(处理器异常解析器),看哪个HandlerExceptionResolver能处理当前异常
//只有当我们自定义了异常解析器或者是使用SpringMVC自带的异常处理器后才会使用这两个容器自带的HandlerExceptionResolver
//本次案例中我们自定义了ResponseStatusExceptionResolver,所以我们容器自带的HandlerExceptionResolver-HandlerExceptionResolverComposite异常解析器组
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//HandlerExceptionResolverComposite解析异常,1.3走这里
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
//多余代码省略
}
1.3HandlerExceptionResolverComposite解析异常
//======================exMv = resolver.resolveException(request, response, handler, ex)=======================================
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
//本次案例中我们自定义了ResponseStatusExceptionResolver,所以使用的是ResponseStatusExceptionResolver来解析异常
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
//=====================AbstractHandlerExceptionResolver.resolveException(request, response, handler, ex)========================
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
//执行标注了@ResponseStatus类的异常处理逻辑
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
//===================ResponseStatusExceptionResolver.doResolveException(request, response, handler, ex)=========================
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
//判断异常是否为ResponseStatusException的子类,这里我们是自定义的UserExcepetion并不是
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
//判断当前的UserExcepetion是否标注了@ResponseStatus,这里显然就是了
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
//把@ResponseStatus的信息组装成了ModelAndView返回了
return resolveResponseStatus(status, request, response, handler, ex);
}
if (ex.getCause() instanceof Exception) {
return doResolveException(request, response, handler, (Exception) ex.getCause());
}
}
catch (Exception resolveEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
}
}
return null;
}
//==============================return resolveResponseStatus(status, request, response, handler, ex)===========================
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
//拿到@ResponseStatus上的vlaue=HttpStatus.FORBIDDEN(403)
int statusCode = responseStatus.code().value();
//拿到@ResponseStatus上的reason = "用户数量太多"
String reason = responseStatus.reason();
//组装成ModelAndView返回
return applyStatusAndReason(statusCode, reason, response);
}
//================================return applyStatusAndReason(statusCode, reason, response)=====================================
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
throws IOException {
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
else {
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
//通过原生的Servlet使用指定的状态码向客户端发送错误响应,并且跳转去一个错误页面403
//并且这个方法执行完成后这个请求结束了,直接通过TomCat发送的/error
response.sendError(statusCode, resolvedReason);
}
//返回了一个空的ModelAndView对象
return new ModelAndView();
}
}
当response.sendError(statusCode, resolvedReason)完成以后,返回的是一个空的ModelAndView对象,所以不会去走render(mv, request, response)的逻辑,而是像8.1.3.2.3发送/error那样的流程一样跳转到错误的页面4xx.html(具体详细流程可以看8.1.3.2.3)
8.1.4.2.3SpringMVC底层的异常,例如参数转换的异常
底层就会被1-2的DefaultHandlerExceptionResolver异常解析器解析到
使用步骤:
不需要使用,对于这些SpringMVC的异常,SpringBoot会自动为我们处理掉
@Controller
public class TableController {
@GetMapping("/editable_table")
public String editable_table(@RequestParam String username){
return "table/editable_table";
}
}
测试效果
处理流程:
真正执行hanlder的方法在8.1.3.2.1详细讲过了,我们暂时忽略,我们之间进入处理派发结果的部分
dispatchException是:
//=====================processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)==================
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//当异常不为空的时候,走这里
if (exception != null) {
//判断异常是否为ModelAndViewDefiningException,本次也不是
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取到原生的handler(public String dynamic_table(Model model))
Object handler = (mappe dHandler != null ? mappedHandler.getHandler() : null);
//处理handler的异常,并且返回一个ModelAndView对象,1.2走这里
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//当1.2完成后,就开始进行页面渲染,页面渲染的流程在5.2.2.2.1很详细了,这里就不作说明
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1.2处理handler的异常,并且返回一个ModelAndView对象
//================================mv = processHandlerException(request, response, handler, exception)===========================
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//遍历所有的HandlerExceptionResolver(处理器异常解析器),看哪个HandlerExceptionResolver能处理当前异常
//只有当我们自定义了异常解析器或者是使用SpringMVC自带的异常处理器后才会使用这两个容器自带的HandlerExceptionResolver
//本次案例中我们使用SpringMVC自带的异常处理器,所以我们容器自带的HandlerExceptionResolver-HandlerExceptionResolverComposite异常解析器组
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//HandlerExceptionResolverComposite解析异常,1.3走这里
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
//多余代码省略
}
1.3HandlerExceptionResolverComposite解析异常
//===========================exMv = resolver.resolveException(request, response, handler, ex)===================================
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
//本次案例中我们使用SpringMVC自带的异常处理,所以使用的是DefaultHandlerExceptionResolver来解析异常
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
}
//========================AbstractHandlerExceptionResolver.resolveException(request, response, handler, ex)====================
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
//执行SpringMVC底层的异常处理逻辑
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
//=========DefaultHandlerExceptionResolver.doResolveException(request, response, handler, ex)==============
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
//因为当前的异常是参数传递异常,所以异常是属于MissingServletRequestParameterException
else if (ex instanceof MissingServletRequestParameterException) {
//同样是返回一个ModelAndView对象
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
//=====return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, response, handler)====
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
//同样是通过原生的Servlet使用指定的状态码向客户端发送错误响应,并且跳转去一个错误页面HttpServletResponse.SC_BAD_REQUEST(400)
//并且这个方法执行完成后这个请求结束了,直接通过TomCat发送的/error
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
同样地当response.sendError(statusCode, resolvedReason)完成以后,返回的是一个空的ModelAndView对象,所以不会去走render(mv, request, response)的逻辑,而是像8.1.3.2.3发送/error那样的流程一样跳转到错误的页面4xx.html(具体详细流程可以看8.1.3.2.3)