springboot的异常处理机制源码解析
本文目录
- springboot默认的异常处理机制
- 默认异常处理规则
- 定制异常处理的逻辑
- 异常处理的自动配置原理
- 异常处理的步骤流程总结及源码解析
1.springboot默认的异常处理机制
默认异常处理规则
● 默认情况下,Spring Boot提供/error处理所有错误的映射。
● 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。
定制异常处理的逻辑(白点为黑点的子项)
● 自定义错误页
○ error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
● @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
● @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
● Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
○ response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
异常处理的自动配置原理
1.问题描述
为什么要剖析自动配置原理:因为上面的一些默认的异常处理行为得益于springboot已经帮助我们配置好的一些东西。
● ErrorMvcAutoConfiguration 自动配置异常处理规则
○ 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
■ public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
■ DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
○ 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
■ 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
■ 容器中有组件 View->id是error;(响应默认错误页)注解:页面响应的ModelAndView(“error”, model);视图名称刚好为error,此处又注入了一个名称为error的view,相当于给页面响应这个视图。(注意只有给浏览器响应时才会找error这个视图)
■ 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。(视图必须由视图解析器得到)这里得到一个StaticView(详细可以看ErrorMvcAutoConfiguration中对于StaticView的注释部分),最终页面显示什么都有这个view决定。
总结: 如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)
○ 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
■ 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面,拼串就是error/viewName.html
■ error/404、5xx.html
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration下定义了异常处理的配置信息
@Configuration(proxyBeanMethods = false) //是否为该Bean创建代理对象
@ConditionalOnWebApplication(type = Type.SERVLET) //是否是一个servlet技术栈的web服务
@ConditionalOnClass({Servlet.class, DispatcherServlet.class}) //条件注解标注的类型的bean在容器中存在,该配置类才生效
@AutoConfigureBefore({WebMvcAutoConfiguration.class}) //定义了配置类的顺序
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class, WebMvcProperties.class}) //定义配置项和javaBean实体的一个绑定关系
public class ErrorMvcAutoConfiguration {
// 引入配置项信息,方便获取
private final ServerProperties serverProperties;
public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Bean
@ConditionalOnMissingBean( //如果没有ErrorAttributes类型的Bean,将new DefaultErrorAttributes() 类型的Bean放入到容器中,按照Springboot的莫认规则,这个组件的id为errorAttributes 即就是方法名称
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
// 如果用户没有向容器中驻入一个ErrorController类型的Bean,则springboot会默认配置一个BasicErrorController
@ConditionalOnMissingBean(
value = {ErrorController.class},
search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
@Bean
public static ErrorMvcAutoConfiguration.PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
return new ErrorMvcAutoConfiguration.PreserveErrorControllerTargetClassPostProcessor();
}
static class PreserveErrorControllerTargetClassPostProcessor implements BeanFactoryPostProcessor {
PreserveErrorControllerTargetClassPostProcessor() {
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] errorControllerBeans = beanFactory.getBeanNamesForType(ErrorController.class, false, false);
String[] var3 = errorControllerBeans;
int var4 = errorControllerBeans.length;
for(int var5 = 0; var5 < var4; ++var5) {
String errorControllerBean = var3[var5];
try {
beanFactory.getBeanDefinition(errorControllerBean).setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
} catch (Throwable var8) {
}
}
}
}
static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
public int getOrder() {
return 0;
}
}
//StaticView是springboot默认注入的一个view对象,通过render方法就可以看出,用于向浏览器响应白页的
private static class StaticView implements View {
private static final MediaType TEXT_HTML_UTF8;
private static final Log logger;
private StaticView() {
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = this.getMessage(model);
logger.error(message);
} else {
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(this.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(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
private String htmlEscape(Object input) {
return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message = message + " and exception [" + model.get("message") + "]";
}
message = message + " as the response has already been committed.";
message = message + " As a result, the response may have the wrong status code.";
return message;
}
public String getContentType() {
return "text/html";
}
static {
TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
}
}
private static class ErrorTemplateMissingCondition extends SpringBootCondition {
private ErrorTemplateMissingCondition() {
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Builder message = ConditionMessage.forCondition("ErrorTemplate Missing", new Object[0]);
TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());
TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(), context.getClassLoader(), context.getResourceLoader());
return provider != null ? ConditionOutcome.noMatch(message.foundExactly("template from " + provider)) : ConditionOutcome.match(message.didNotFind("error template view").atAll());
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "server.error.whitelabel",
name = {"enabled"},
matchIfMissing = true
)
@Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
protected static class WhitelabelErrorViewConfiguration {
private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
protected WhitelabelErrorViewConfiguration() {
}
// 向容器中放一个view,这个view默认类型是ErrorMvcAutoConfiguration.StaticView
@Bean(
name = {"error"}
)
@ConditionalOnMissingBean(
name = {"error"}
)
public View defaultErrorView() {
return this.defaultErrorView;
}
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(2147483637);
return resolver;
}
}
@Configuration(
proxyBeanMethods = false
)
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
//配置一个错误的视图解析器 如果容器中没有时增加一个DefaultErrorViewResolver
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean({ErrorViewResolver.class})
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
}
上面提到的BasicErrorController带注释详解
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"}) //如果说没有配置server.error.path项取${error.path:/error}而error.path也没有配置,则取默认的/error,总体来说是处理/error的请求
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
public String getErrorPath() {
return null;
}
//如果是浏览器 当发生异常时会响应一个白页 响应一个HTML页面
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//如果所有的异常解析器都不能处理异常,就会被springMVC底层默认转发到一个/error请求,由于在第一个异常解析器DefaultErrorAttribute解析时将异常信息放入到request请求域中,这里可以获取到。
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
// 如果是postman等,将响应一个ResponseEntity 即就是JSON格式数据
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
HttpStatus status = this.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(new Include[]{Include.EXCEPTION});
}
if (this.isIncludeStackTrace(request, mediaType)) {
options = options.including(new Include[]{Include.STACK_TRACE});
}
if (this.isIncludeMessage(request, mediaType)) {
options = options.including(new Include[]{Include.MESSAGE});
}
if (this.isIncludeBindingErrors(request, mediaType)) {
options = options.including(new Include[]{Include.BINDING_ERRORS});
}
return options;
}
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
switch(this.getErrorProperties().getIncludeStacktrace()) {
case ALWAYS:
return true;
case ON_PARAM:
case ON_TRACE_PARAM:
return this.getTraceParameter(request);
default:
return false;
}
}
protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
switch(this.getErrorProperties().getIncludeMessage()) {
case ALWAYS:
return true;
case ON_PARAM:
return this.getMessageParameter(request);
default:
return false;
}
}
protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
switch(this.getErrorProperties().getIncludeBindingErrors()) {
case ALWAYS:
return true;
case ON_PARAM:
return this.getErrorsParameter(request);
default:
return false;
}
}
protected ErrorProperties getErrorProperties() {
return this.errorProperties;
}
}
查看一下自动配置类中帮助我们配置的DefaultErrorViewResolver(核心看注释部分)
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);
}
private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* Create a new {@link DefaultErrorViewResolver} instance.
* @param applicationContext the source application context
* @param resourceProperties resource properties
*/
public DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
}
DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties,
TemplateAvailabilityProviders templateAvailabilityProviders) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.templateAvailabilityProviders = templateAvailabilityProviders;
}
//视图解析器解析得到视图
@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;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认的错误的视图名称就为 error/请求的状态码 viewName就是http请求的状态码,因此就是我们为什么在error文件夹下定义 4xx.html 和5xx.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);
}
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;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* {@link View} backed by an HTML resource.
*/
private static class HtmlResourceView implements View {
private Resource resource;
HtmlResourceView(Resource resource) {
this.resource = resource;
}
@Override
public String getContentType() {
return MediaType.TEXT_HTML_VALUE;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setContentType(getContentType());
FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
}
}
}
接下来看看ErrorMvcAutoConfiguration帮我们自动配置的DefaultErrorAttributes有什么作用
@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";
private final Boolean includeException;
public DefaultErrorAttributes() {
this.includeException = null;
}
/** @deprecated */
@Deprecated
public DefaultErrorAttributes(boolean includeException) {
this.includeException = includeException;
}
public int getOrder() {
return -2147483648;
}
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
this.storeErrorAttributes(request, ex);
return null;
}
//保存错误的信息 包含的内容
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
//保存异常信息到request域中 至于为什么继续看后边
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = this.getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (Boolean.TRUE.equals(this.includeException)) {
options = options.including(new Include[]{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;
}
/** @deprecated */
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
//时间戳
errorAttributes.put("timestamp", new Date());
//状态码
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
//添加路径
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
if (status == null) {
errorAttributes.put("status", 999);
errorAttributes.put("error", "None");
} else {
errorAttributes.put("status", status);
try {
errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
} catch (Exception var5) {
errorAttributes.put("error", "Http Status " + status);
}
}
}
//保存一些异常的详细信息
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
Throwable error = this.getError(webRequest);
if (error != null) {
while(true) {
if (!(error instanceof ServletException) || error.getCause() == null) {
errorAttributes.put("exception", error.getClass().getName());
if (includeStackTrace) {
this.addStackTrace(errorAttributes, error);
}
break;
}
error = error.getCause();
}
}
this.addErrorMessage(errorAttributes, webRequest, error);
}
private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
BindingResult result = this.extractBindingResult(error);
if (result == null) {
this.addExceptionErrorMessage(errorAttributes, webRequest, error);
} else {
this.addBindingResultErrorMessage(errorAttributes, result);
}
}
//异常响应的message信息
private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
if (StringUtils.isEmpty(message) && error != null) {
message = error.getMessage();
}
if (StringUtils.isEmpty(message)) {
message = "No message available";
}
errorAttributes.put("message", message);
}
private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result) {
errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());
errorAttributes.put("errors", result.getAllErrors());
}
private BindingResult extractBindingResult(Throwable error) {
if (error instanceof BindingResult) {
return (BindingResult)error;
} else {
return error instanceof MethodArgumentNotValidException ? ((MethodArgumentNotValidException)error).getBindingResult() : null;
}
}
//异常响应的堆栈信息
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put("trace", stackTrace.toString());
}
//发生异常的访问路径
private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
String path = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");
if (path != null) {
errorAttributes.put("path", path);
}
}
public Throwable getError(WebRequest webRequest) {
Throwable exception = (Throwable)this.getAttribute(webRequest, ERROR_ATTRIBUTE);
return exception != null ? exception : (Throwable)this.getAttribute(webRequest, "javax.servlet.error.exception");
}
private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
return requestAttributes.getAttribute(name, 0);
}
}
2.异常处理的步骤流程总结及源码解析
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException 封装。
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
● 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
● 2、具体的异常解析器有那些呢?默认两个,第二个又包含三个异常处理器
○ 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
○ 2、默认没有任何异常解析器能处理异常,所以异常会被抛出
■ 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理。BasicErrorController源码在上面自动配置过。
■ 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
调试异常处理的流程,首先来到 org.springframework.web.servlet.DispatcherServlet的doDispatch方法。(这里需要对SpringMVC的底层有一定的了解)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//1.判断是否是一个文件上传的请求 如果是一个文件上传的请求就包装成为一个multipartRequestParsed
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//2.得到请求与相应的能处理该请求的控制器的映射关系(哪个类能处理那个请求的一一对应关系)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//3.根据请求的控制器得到能处理该请求的适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//通过适配器执行目标方法(重点)适配器就相当于一个反射工具 通过反射执行方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//保存一个默认的视图名称
this.applyDefaultViewName(processedRequest, mv);
//过滤器相关
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
//发生异常就创建一个 dispatchException
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//无论执行目标方法有无异常都会进入视图解析流程,如果视图解析流程发生异常进入后续catch
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
进入视图解析方法
//参数说明 mappedHandler:那个控制器的那个方法发生异常 mv视图名称和数据(如果发生异常mv为NULL) exception异常信息
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
boolean errorView = false;
//发生异常进入
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
//处理handler的异常,无论为空还是不为空最终都返回一个ModelAndView
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
//如何处理Handler发生的异常呢?
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
ModelAndView exMv = null;
//handler的异常解析器(多个 遍历看那个能处理)
if (this.handlerExceptionResolvers != null) {
Iterator var6 = this.handlerExceptionResolvers.iterator();
while(var6.hasNext()) {
HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
//直到有一个异常解析器能够解析才能跳出
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
进入resolveException(request, response, handler, ex);方法来到org.springframework.boot.web.servlet.error.DefaultErrorAttributes中
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
this.storeErrorAttributes(request, ex);
return null;
}
//将异常信息设置到request域中 直接返回NULL了 上面提到过
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
剖析一下HandlerExceptionResolver的作用
// var3代表那个方法发生了异常 var4 发生的异常信息是什么 最终返回ModelAndView
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}
由于DefaultErrorAttributes这个异常解析器只是保存异常信息到请求域就返回了,所以来到第二个异常解析器HandlerExceptionResolverComposite 是一个组合异常解析器,包含三个异常解析器,这三个异常解析器大致可以解析以下三种情况。
1.ExceptionHandlerExceptionResolver用于处理标注了@ExceptionHandler注解的情况
2.ResponseStatusExceptionResolver用来处理标注了@ResponseStatus注解的情况
3.DefaultHandlerExceptionResolver
最终结果是:这三个异常处理器都不能处理,异常被抛出。
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
Iterator var5 = this.resolvers.iterator();
while(var5.hasNext()) {
HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var5.next();
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
#### 默认被BasicErrorController处理器处理/error请求的细节进入org.springframework.boot.autoconfigure.web.servlet.error.asicErrorController 的errorHtml()方法的 resolveErrorView(request, response, status, model);中
```java
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
//遍历所有的错误视图解析器 看那个能解析 这里默认就是DefaultErrorViewResolver 上面在自动配置中注入过DefaultErrorViewResolver
Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
进入resolveErrorView(request, status, model);方法中来到DefaultErrorViewResolver
@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;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//自动拼串为 error/4xx.html (viewName就是请求的状态码)
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
展现在代码上就是如下的格式,作用是用于灵活定制自己的页面。
接下来对目录提到的 定制异常处理的逻辑进行详细的说明
@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//自定义的全局异常处理器
@ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //处理异常 该方法可以返回一个ModelAndView 这里为了简便,只做页面跳转,不携带数据
public String handleArithException(Exception e){
log.error("异常是:{}",e);
return "login"; //视图地址
}
}
@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);就是tomcat发送的/error。被BasicErrorController处理,上面已经讲过。
//value指明一个错误的状态码 reason错误的原因
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {
public UserTooManyException(){
}
public UserTooManyException(String message){
super(message);
}
}
>Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
○ response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());交由tomcat发送/error请求去处理。
如果没有解析器能处理,tomcat那个非常丑的异常页就会出来。
```java
//自定义的异常解析器 如果被框架异常解析器处理了 就是优先级问题(因为遍历异常解析器会按顺序解析)
@Order(value= Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
response.sendError(511,"我喜欢的错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}