10、SpringBoot2 Web开发之异常处理及其原理

1、SpringBoot默认处理

1.1 错误展示

1.1.1 浏览器访问

当浏览器访问不存在的请求或者发生异常的请求,会根据返回的状态值匹配error目录下的页面进行展示,先精确匹配,后模糊匹配,当未配置时返回默认空白页。
状态值配置如下:

在这里插入图片描述

空白页如下:

在这里插入图片描述

1.1.2 postman等客户访问

当客户端进行访问时,会返回一个错误串

{
    "timestamp": "2023-03-22T12:56:34.054+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/aa"
}

1.2 页面跳转的原理

对于这些错误的请求,系统无法进行处理,servlet底层会重新发送一个/error请求,该请求会被BasicErrorController进行处理

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
}

1.3 异常处理自动配置原理

1.3.1 ErrorMvcAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
}

服务启动时,会自动往容器中添加几个异常处理的组件

  • DefaultErrorAttributes

定义错误页面中可以包含哪些数据

   @Bean
   @ConditionalOnMissingBean(
       value = {ErrorAttributes.class},
       search = SearchStrategy.CURRENT
   )
   public DefaultErrorAttributes errorAttributes() {
       return new DefaultErrorAttributes();
   }
  • BasicErrorController

处理默认 /error 路径的请求

@Bean
 @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()));
 }
  • BeanNameViewResolver

根据返回的名称匹配视图

        @Bean
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(2147483637);
            return resolver;
        }
  • View

返回一个名称为error的默认视图,即为空白页

  @Bean(
      name = {"error"}
  )
  @ConditionalOnMissingBean(
      name = {"error"}
  )
  public View defaultErrorView() {
      return this.defaultErrorView;
  }

返回的默认视图,即空白页的由来

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());
        }
    }
}
  • DefaultErrorViewResolver

默认的错误视图解析器

 @Bean
  @ConditionalOnBean({DispatcherServlet.class})
  @ConditionalOnMissingBean({ErrorViewResolver.class})
  DefaultErrorViewResolver conventionErrorViewResolver() {
      return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
  }

1.3.2 BasicErrorController处理流程

1.3.2.1 页面处理流程

会根据produces = {“text/html”}判断处理浏览器访问的请求

 @RequestMapping(
     produces = {"text/html"}
 )
 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
     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);
 }

resolveErrorView方法会根据容器中错误视图解析器进行解析,默认只有一个DefaultErrorViewResolver,自动注入时注入

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
     Iterator var5 = this.errorViewResolvers.iterator();

     ModelAndView modelAndView;
     do {
         if (!var5.hasNext()) {
             return null;
         }

         ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
         modelAndView = resolver.resolveErrorView(request, status, model);
     } while(modelAndView == null);

     return modelAndView;
 }

DefaultErrorViewResolver使用resolveErrorView解析视图,判断模板引擎下error是否配置符合当前状态值的页面,存在则返回该视图,不存在则返回为空

public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
      ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
      if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
          modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
      }

      return modelAndView;
  }

private ModelAndView resolve(String viewName, Map<String, Object> model) {
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
    return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}

当返回为空时,系统会根据BeanNameViewResolver解析error,使用名称为error的StaticView进行解析返回。

return modelAndView != null ? modelAndView : new ModelAndView("error", model);
1.3.2.2 客户端处理流程

获取错误信息,并返回

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

使用DefaultErrorAttributes获取错误信息

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

2、自定义错误返回

2.1 自定义返回形式

2.2.1 @ControllerAdvice + @ExceptionHandler

ExceptionHandler对异常进行捕获,若能处理则进行处理,按照ExceptionHandler的方法进行处理返回。也可使用@RestControllerAdvice返回字符串。

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //处理异常
    public String handleArithException(Exception e){

        log.error("异常是:{}",e);
        return "login"; //视图地址
    }
}

2.2.2 @ResponseStatus+自定义异常

代码发生自定义异常,会根据自定义异常上面ResponseStatus定义的状态值进行异常返回

@ResponseStatus(value= HttpStatus.FORBIDDEN)//403异常
public class ResponseStatusException extends RuntimeException {

    public  ResponseStatusException(){

    }
    public  ResponseStatusException(String message){
        super(message);
    }
}

当系统发生ResponseStatusException异常时,会返回根据状态值403进行解析返回
在这里插入图片描述

2.2.3 自定义异常处理器

继承HandlerExceptionResolver,将优先级提到最高,优先使用自定义异常处理器进行处理

@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(911,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

当系统发生异常时,根据自定义的911状态值进行返回
在这里插入图片描述

2.2 自定义返回原理

2.2.1 返回流程

执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException进行封装

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try{
	/****
	}
	catch (Exception ex) {
		dispatchException = ex;
	}
	catch (Throwable err) {
		dispatchException = new NestedServletException("Handler dispatch failed", err);
	}
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

processDispatchResult判断异常是否为空,不为空进行异常处理

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) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}
	//..............
}

遍历所有的异常解析器,看能否解析异常

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

在这里插入图片描述

2.2.2 异常解析器

2.2.2.1 DefaultErrorAttributes解析器

将错误信息保存,不做人户处理

public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    this.storeErrorAttributes(request, ex);
    return null;
}
2.2.2.2 HandlerExceptionResolverComposite解析器

循环解析所有异常解析器,直到找到能处理的

public ModelAndView resolveException(
	HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	if (this.resolvers != null) {
		for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
			ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (mav != null) {
				return mav;
			}
		}
	}
	return null;
}

在这里插入图片描述

2.2.2.2.1 ExceptionHandlerExceptionResolver

处理@ControllerAdvice+@ExceptionHandler

//ExceptionHandlerExceptionResolver
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
	//.................
	exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
	//.................
}
//ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
	//获取返回值
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	//...............
	//将ExceptionHandler对应的结果进行处理返回  同接口返回逻辑
	this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	//..............
}
2.2.2.2.2 ResponseStatusExceptionResolver

处理@ResponseStatus+自定义异常,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason),底层tomcat发送/error请求

//ResponseStatusExceptionResolver
protected ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	//.................
	ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
	if (status != null) {
		return resolveResponseStatus(status, request, response, handler, ex);
	}
	//.................
}
//ResponseStatusExceptionResolver
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
	int statusCode = responseStatus.code().value();
	String reason = responseStatus.reason();
	return applyStatusAndReason(statusCode, reason, response);
}
//ResponseStatusExceptionResolver
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);
		//
		response.sendError(statusCode, resolvedReason);
	}
	return new ModelAndView();
}
2.2.2.2.3 DefaultHandlerExceptionResolver

Spring底层的异常,如 参数类型转换异常;response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage())

//DefaultHandlerExceptionResolver
//一下异常进行发送
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);
		}
		else if (ex instanceof MissingServletRequestParameterException) {
			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;
}
//发送异常
protected ModelAndView handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex,
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

	if (!response.isCommitted()) {
		response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
	}
	else {
		logger.warn("Async request timed out");
	}
	return new ModelAndView();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值