springboot-异常处理使用与原理解析
目錄
一、springboot的默認異常處理規則
1.瀏覽器訪問不存在的資源會返回404頁面
2.用postman訪問不存在的資源會響應json
?3.如果用瀏覽器訪問資源后臺報錯,會響應500錯誤頁面
4.如果用postman訪問資源后臺報錯,會響應json數據
??
二、自定義錯誤頁面
1.設置自定義錯誤頁面
2.500頁面取出錯誤信息
三、異常處理的自動配置原理
1.ErrorMvcAutoConfiguration
2.BasicErrorController
3.DefaultErrorViewResolver
4.DefaultErrorAttributes
5.總結
四、默認異常處理流程
1.springmvc處理邏輯參考博文
2.DispatcherServlet的doDispatch方法
3.視圖解析流程
4.處理handler發生的異常
5.系統默認的異常解析器
6.如果沒有任何處理器能處理異常
五、自定義錯誤處理邏輯
1.自定義錯誤頁面
2.@ControllerAdvice+@ExceptionHandler處理全局異常(推薦)
3.@ResponseStatus+自定義異常
4.Spring底層的異常
5.自定義異常解析器
一、springboot的默認異常處理規則
1.瀏覽器訪問不存在的資源會返回404頁面
2.用postman訪問不存在的資源會響應json
?3.如果用瀏覽器訪問資源后臺報錯,會響應500錯誤頁面
4.如果用postman訪問資源后臺報錯,會響應json數據
?
?
二、自定義錯誤頁面
1.設置自定義錯誤頁面
(1)在靜態資源目錄下(/static (or /public or /resources or /META-INF/resources),或者模板目錄下(/templates)?放一個/error目錄。
然后在里面放上對應的4xx.html、5xx.html。
再在瀏覽器訪問4xx錯誤、5xx錯誤,就會顯示新增的4xx.html、5xx.html頁面。
2.500頁面取出錯誤信息
會將以下的信息放到請求域中。
${message} // 錯誤信息 ${trace} // 堆棧信息 ${status} // 錯誤碼?
三、異常處理的自動配置原理
1.ErrorMvcAutoConfiguration
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 // 在mvc配置好之后再配置該類 @AutoConfigureBefore(WebMvcAutoConfiguration.class) // 綁定一些配置屬性 @EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class }) public class ErrorMvcAutoConfiguration {private final ServerProperties serverProperties;public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {this.serverProperties = serverProperties;}// 注冊一個DefaultErrorAttributes組件,id為errorAttributes,實現了ErrorAttributes, HandlerExceptionResolver, Ordered接口@Bean@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)public DefaultErrorAttributes errorAttributes() {return new DefaultErrorAttributes();}// 注冊一個BasicErrorController,id為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()));}// 錯誤頁的定制化器@Beanpublic ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);}@Beanpublic static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {return new PreserveErrorControllerTargetClassPostProcessor();}@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;}// 配置一個錯誤視圖解析器@Bean@ConditionalOnBean(DispatcherServlet.class)@ConditionalOnMissingBean(ErrorViewResolver.class)DefaultErrorViewResolver conventionErrorViewResolver() {return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);}}@Configuration(proxyBeanMethods = false)@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)@Conditional(ErrorTemplateMissingCondition.class)protected static class WhitelabelErrorViewConfiguration {private final StaticView defaultErrorView = new StaticView();// 容器中還會放一個View組件,id為error,該View會響應默認錯誤頁@Bean(name = "error")@ConditionalOnMissingBean(name = "error")public View defaultErrorView() {return this.defaultErrorView;}// If the user adds @EnableWebMvc then the bean name view resolver from// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.// 容器中放組件 BeanNameViewResolver(視圖解析器);按照返回的視圖名作為組件的id去容器中找View對象。@Bean@ConditionalOnMissingBeanpublic BeanNameViewResolver beanNameViewResolver() {BeanNameViewResolver resolver = new BeanNameViewResolver();resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);return resolver;}}/*** {@link SpringBootCondition} that matches when no error template view is detected.*/private static class ErrorTemplateMissingCondition extends SpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage.Builder message = ConditionMessage.forCondition("ErrorTemplate Missing");TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(),context.getClassLoader(), context.getResourceLoader());if (provider != null) {return ConditionOutcome.noMatch(message.foundExactly("template from " + provider));}return ConditionOutcome.match(message.didNotFind("error template view").atAll());}}/*** Simple {@link View} implementation that writes a default HTML error page.響應默認錯誤頁*/private static class StaticView implements View {private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);private static final Log logger = LogFactory.getLog(StaticView.class);@Overridepublic 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());}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());}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 += " and exception [" + model.get("message") + "]";}message += " as the response has already been committed.";message += " As a result, the response may have the wrong status code.";return message;}@Overridepublic String getContentType() {return "text/html";}}…… }2.BasicErrorController
處理默認 /error 路徑的請求,可以通過配置server.error.path或者error.path修改。
頁面響應 new ModelAndView("error", model);
可以依據內容協商 響應白頁(瀏覽器)、json(客戶端)。
內容協商參考博文:https://blog.csdn.net/A_art_xiang/article/details/122242942
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController {private final ErrorProperties errorProperties;/*** Create a new {@link BasicErrorController} instance.* @param errorAttributes the error attributes* @param errorProperties configuration properties*/public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {this(errorAttributes, errorProperties, Collections.emptyList());}/*** Create a new {@link BasicErrorController} instance.* @param errorAttributes the error attributes* @param errorProperties configuration properties* @param errorViewResolvers error view resolvers*/public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,List<ErrorViewResolver> errorViewResolvers) {super(errorAttributes, errorViewResolvers);Assert.notNull(errorProperties, "ErrorProperties must not be null");this.errorProperties = errorProperties;}@Overridepublic String getErrorPath() {return null;}// 寫出html,尋找error視圖@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);return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);}// 寫出去json數據@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity<>(status);}Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));return new ResponseEntity<>(body, status);}@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {HttpStatus status = getStatus(request);return ResponseEntity.status(status).build();}……}3.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;}// 解析得到視圖對象@Overridepublic 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); //獲取跳轉的頁面,以http狀態碼命名的html頁面。}return modelAndView;}private ModelAndView resolve(String viewName, Map<String, Object> model) {String errorViewName = "error/" + viewName; // 添加一個/errpr/+視圖名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;}@Overridepublic 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;}@Overridepublic String getContentType() {return MediaType.TEXT_HTML_VALUE;}@Overridepublic void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)throws Exception {response.setContentType(getContentType());FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());}} }4.DefaultErrorAttributes
DefaultErrorAttributes:定義錯誤頁面中可以包含哪些數據。
@Order(Ordered.HIGHEST_PRECEDENCE) public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";private final Boolean includeException;/*** Create a new {@link DefaultErrorAttributes} instance.*/public DefaultErrorAttributes() {this.includeException = null;}/*** Create a new {@link DefaultErrorAttributes} instance.* @param includeException whether to include the "exception" attribute* @deprecated since 2.3.0 in favor of* {@link ErrorAttributeOptions#including(Include...)}*/@Deprecatedpublic DefaultErrorAttributes(boolean includeException) {this.includeException = includeException;}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}// 返回ModelAndview,一個最終跳轉的頁面地址@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {// 保存錯誤的屬性storeErrorAttributes(request, ex);return null;}private void storeErrorAttributes(HttpServletRequest request, Exception ex) {request.setAttribute(ERROR_ATTRIBUTE, ex);}// 可以保存的屬性 @Overridepublic Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));if (Boolean.TRUE.equals(this.includeException)) {options = options.including(Include.EXCEPTION);}if (!options.isIncluded(Include.EXCEPTION)) {errorAttributes.remove("exception");}if (!options.isIncluded(Include.STACK_TRACE)) {errorAttributes.remove("trace");}if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {errorAttributes.put("message", "");}if (!options.isIncluded(Include.BINDING_ERRORS)) {errorAttributes.remove("errors");}return errorAttributes;}// 屬性@Override@Deprecatedpublic 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;}// 屬性private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {Integer status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);if (status == null) {errorAttributes.put("status", 999);errorAttributes.put("error", "None");return;}errorAttributes.put("status", status);try {errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());}catch (Exception ex) {// Unable to obtain a reasonerrorAttributes.put("error", "Http Status " + status);}}// 異常信息private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,boolean includeStackTrace) {Throwable error = getError(webRequest);if (error != null) {while (error instanceof ServletException && error.getCause() != null) {error = error.getCause();}errorAttributes.put("exception", error.getClass().getName());if (includeStackTrace) {addStackTrace(errorAttributes, error);}}addErrorMessage(errorAttributes, webRequest, error);}private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {BindingResult result = extractBindingResult(error);if (result == null) {addExceptionErrorMessage(errorAttributes, webRequest, error);}else {addBindingResultErrorMessage(errorAttributes, result);}}private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {Object message = getAttribute(webRequest, RequestDispatcher.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;}if (error instanceof MethodArgumentNotValidException) {return ((MethodArgumentNotValidException) error).getBindingResult();}return 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 = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);if (path != null) {errorAttributes.put("path", path);}}@Overridepublic Throwable getError(WebRequest webRequest) {Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);return (exception != null) ? exception : getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);}@SuppressWarnings("unchecked")private <T> T getAttribute(RequestAttributes requestAttributes, String name) {return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);}}5.總結
錯誤 -> /error ->?BasicErrorController ->?按照bean名字查找錯誤view -> DefaultErrorViewResolver以http狀態作為視圖頁地址找到視圖(404/5xx) -> 如果是瀏覽器就響應白頁,postman就響應json
四、默認異常處理流程
1.springmvc處理邏輯參考博文
springBoot-springMVC請求處理原理_私人博客,有需要請聯系17854238061(vx同號)-CSDN博客
2.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);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.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;}// 執行目標方法,目標方法有任何異常都會被catch住// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}// 執行目標方法,目標方法運行期間有任何異常都會被catch、而且標志當前請求結束;并且用 dispatchException封裝。catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 進入視圖解析流程(是否出現異常都會執行),如果出現異常,mv就會是null// 默認最終會拋出一個異常,被下面捕獲(詳見4、5)processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}// 異常被捕獲,但是未處理,又拋出異常。catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}} }3.視圖解析流程
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);// 處理handler的異常。mv會被重新定義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 forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);} }4.處理handler發生的異常
處理handler發生的異常,處理完成返回ModelAndView
@Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// 移出request中的一些屬性// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;// 遍歷所有的 handlerExceptionResolvers,看誰能處理當前異常【HandlerExceptionResolver是一個處理器異常解析器】if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 調用異常解析器的resolveException方法,默認沒有任何人能處理異常,所以異常會被拋出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; }5.系統默認的異常解析器
(1)DefaultErrorAttributes先來處理異常。把異常信息保存到request域,并且返回null
// org.springframework.boot.web.servlet.error.DefaultErrorAttributes#resolveException @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {// 保存錯誤屬性storeErrorAttributes(request, ex);return null; }?(2)HandlerExceptionResolverComposite里面有三個resolver,會遍歷執行
// org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException @Override @Nullable 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; }(3)其他三個解析器會走父類
// org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException @Override @Nullable public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);// 解析異常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("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));}// Explicitly configured warn logger in logException method.logException(ex, request);}return result;}else {return null;} }6.如果沒有任何處理器能處理異常
(1)最終底層就會轉發到 /error 請求,/error請求會被底層的BasicErrorController處理(詳看上面)。
(2)解析錯誤視圖;遍歷所有的 ErrorViewResolver 看誰能解析。
(3)默認的 DefaultErrorViewResolver ,作用是把響應狀態碼作為錯誤頁的地址,error/500.html
(4)模板引擎最終響應這個頁面 error/500.html
五、自定義錯誤處理邏輯
1.自定義錯誤頁面
詳情見上面。
2.@ControllerAdvice+@ExceptionHandler處理全局異常(推薦)
@ControllerAdvice+@ExceptionHandler處理全局異常;底層是 ExceptionHandlerExceptionResolver 支持的
import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;/** * 處理整個web controller的異常 */ @Slf4j @ControllerAdvice public class GlobalExceptionHandler {@ResponseBody // 可以不加,返回一個視圖@ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //指定處理的異常public String handleArithException(Exception e){log.error("異常是:{}",e);return "exception handle success";} }3.@ResponseStatus+自定義異常
用處:發生該異常會響應指定的錯誤碼,可以定義錯誤碼對應的錯誤頁面。
底層是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底層調用 response.sendError(statusCode, resolvedReason);相當于給tomcat發送的/error。
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus;// 返回一個狀態碼 @ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "我的自定義異常") public class MyException extends RuntimeException {public MyException(){}public MyException(String message){super(message);} }4.Spring底層的異常
Spring底層封裝了一些異常,這些異常會有默認的異常處理邏輯。
如:
參數類型轉換異常;DefaultHandlerExceptionResolver,也是會調用 response.sendError(statusCode, resolvedReason);相當于給tomcat發送的/error。
5.自定義異常解析器
import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@Order(value= Ordered.HIGHEST_PRECEDENCE) //優先級,數字越小優先級越高 @Component public class MyHandlerExceptionResolver implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response,Object handler, Exception ex) {try {// 寫出數據response.sendError(511,"我喜歡的錯誤");} catch (IOException e) {e.printStackTrace();}return new ModelAndView();} }總結
以上是生活随笔為你收集整理的springboot-异常处理使用与原理解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot-springmvc
- 下一篇: JVM常用启动参数大全(附带解释)