【Spring】HandlerExceptionResolver异常处理器工作原理分析
HandlerExceptionResolver Spring 全局异常处理
常用异常处理器
- DefaultErrorAttributes: 用于BasicErrorController用于错误信息视图展示
- ExceptionHandlerExceptionResolver: 请求和响应参数处理异常
- ResponseStatusExceptionResolver: 响应状态码异常处理: @ResponseStatus 指定特定响应码异常处理,例如设置自定义提示信息
- DefaultHandlerExceptionResolver: 默认异常处理器,常见例如:HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException
HandlerExceptionResolver工作原理
1. 自定义异常处理器
源码:org.springframework.web.servlet.DispatcherServlet.initHandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet "" + getServletName() +
"": using default strategies from DispatcherServlet.properties");
}
}
}
结果:默认 2个异常处理器
DefaultErrorAttributes
HandlerExceptionResolverComposite:
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
2. 异常处理逻辑
源码:org.springframework.web.servlet.DispatcherServlet.processHandlerException
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;
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);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
3. 异常案例:HttpRequestMethodNotSupportedException
异常处理器:DefaultHandlerExceptionResolver
源码:org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException
@Override @Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
// 目标方法Http请求方法不支持
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
//Http 媒体类型不支持
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
// 不接收指定类型媒体类型
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
// http请求路径变量参数缺失:例如@PathVariable指定的变量名,http请求路径为指定对应占位符
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
// 请求参数缺失
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
// 请求绑定异常
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
// 转换错误:例如将String转成Integer
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;
}
4. HttpRequestMethodNotSupportedException详细处理
源码:
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
// 原生Sevlet API响应 405 请求, Tomcat按照ErrorPage路径处理
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView();
}
5. Tomcat 错误页处理
Tomcat默认配置:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.configureContext
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
// Tomcat错误页处理:ErroPage路径配置在:ErrorProperties path(@Value("${error.path:/error}")),默认Error
for (ErrorPage errorPage : getErrorPages()) {
new TomcatErrorPage(errorPage).addToContext(context);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
6. Tomcat ErrorPage 转发处理
源码:org.apache.catalina.core.StandardHostValve.custom
private boolean custom(Request request, Response response, ErrorPage errorPage) {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("Processing " + errorPage);
}
try {
// Forward control to the specified location
ServletContext servletContext = request.getContext().getServletContext();
RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation());
if (rd == null) {
container.getLogger().error(sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation()));
return false;
}
if (response.isCommitted()) {
// Response is committed - including the error page is the
// best we can do
rd.include(request.getRequest(), response.getResponse());
} else {
// Reset the response (keeping the real error code and message)
response.resetBuffer(true);
response.setContentLength(-1);
// 请求转发:/error
rd.forward(request.getRequest(), response.getResponse());
// If we forward, the response is suspended again
response.setSuspended(false);
}
// Indicate that we have successfully processed this custom page
return true;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Report our failure to process this custom page
container.getLogger().error("Exception Processing " + errorPage, t);
return false;
}
}
7. Tomcat ErrorPage(/error) 请求转发(BasicErrorController),完成异常信息响应
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error
/*** 完成异常信息视图处理
/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
// 从异常处理器errorAttributes 取出相关异常信息
protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
//获取异常状态码
protected HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
测试异常提示:
{ "timestamp": "2020-06-22T02:29:27.424+0000",
"status": 405,
"error": "Method Not Allowed",
"message": "Request method "GET" not supported",
"path": "/dev/file/down"
}
以上是 【Spring】HandlerExceptionResolver异常处理器工作原理分析 的全部内容, 来源链接: utcz.com/z/517715.html