异常处理

默认规则

1、默认情况下,Spring Boot 提供 /error 处理所有错误的映射

2、对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息

3、对于浏览器客户端,响应一个“ whitelabel”错误视图,以 HTML 格式呈现相同的数据

4、要对其进行自定义,添加 View 解析为 error

5、要完全替换默认行为,可以实现 ErrorController,并注册该类型的 Bean 定义,或添加 ErrorAttributes 类型的组件,以使用现有机制但替换其内容

 

底层组件

1、ErrorMvcAutoConfiguration:自动配置异常处理规则

2、DefaultErrorAttributes

(1)id:errorAttributes

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

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

3、BasicErrorController

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

(2)处理默认 /error 路径的请求,适配响应:JSON + 白页

@RequestMapping("${server.error.path:${error.path:/error}}")

(3)页面响应

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

(4)JSON 响应

@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));
    return new ResponseEntity<>(body, status);
}

4、View

(1)id:error

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

(2)响应默认错误页

(3)StaticView:error 视图,默认为白页

private final StaticView defaultErrorView = new StaticView();

5、BeanNameViewResolver

(1)id:beanNameViewResolver

@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
    BeanNameViewResolver resolver = new BeanNameViewResolver();
    resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    return resolver;
}

(2)视图解析器:按照返回的视图名,作为组件的 id,查找容器中 View 对象

6、DefaultErrorViewResolver

(1)id:conventionErrorViewResolver

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

(2)如果发生错误,以 HTTP 的状态码作为视图页地址(viewName),查找在 /error 对应页面

 

流程

1、第一次请求

(1)执行目标方法,捕获目标方法运行期间任何异常,标志当前请求结束,并且使用 dispatchException 记录

(2)进入视图解析流程(页面渲染)

(3)处理 handler 发生的异常,处理完成返回 ModelAndView

(4)遍历 handlerExceptionResolvers,查找 HandlerExceptionResolver(处理器异常解析器)并处理当前异常

(5)DefaultErrorAttributes 先处理异常,把异常信息保存到请求域,并且返回 null

(6)默认没有任何处理器异常解析器能处理异常,则抛出异常

2、第二次请求

(1)最终底层发送 /error 请求,由底层 BasicErrorController 处理

(2)遍历所有 errorViewResolvers,查找 ErrorViewResolver(错误视图解析器)并解析错误视图

(3)默认错误视图解析器:DefaultErrorViewResolver,把响应状态码作为错误页的地址,如:error/404.html

(4)根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400

(5)尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html

(6)若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到(7)

(7)依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到(8)

(8)将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转(9)

(9)处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)

 

源码

1、DispatcherServlet 类

(1)doDispatch 方法

//使用 dispatchException 记录异常
Exception dispatchException = null;
//执行目标方法
//底层发送/error请求,由BasicErrorController处理
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//捕获目标方法运行期间任何异常
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);
}

(2)processDispatchResult 方法

//进入视图解析流程(页面渲染),处理handler发生的异常,处理完成返回ModelAndView
mv = processHandlerException(request, response, handler, exception);

(3)processHandlerException 方法

@Nullable
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) {
        //遍历所有HandlerExceptionResolver,查找 HandlerExceptionResolver(处理器异常解析器)并处理当前异常
        //DefaultErrorAttributes 先处理异常,把异常信息保存到请求域,并且返回 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;
}

2、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())) {
        //尝试以 4xx 或 5xx 作为错误页面页面进行解析
        modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
    }
    return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {
    //错误模板页面,例如 error/404、error/4xx、error/500、error/5xx
    String errorViewName = "error/" + viewName;
    //当模板引擎可以解析这些模板页面时,就用模板引擎解析
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                                                                                           this.applicationContext);
    if (provider != null) {
        //在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图
        return new ModelAndView(errorViewName, model);
    }
    //若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面
    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);
            //静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
            resource = resource.createRelative(viewName + ".html");
            //若静态资源文件夹下存在以上错误页面,则直接返回
            if (resource.exists()) {
                return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
            }
        } catch (Exception ex) {
        }
    }
    return null;
}

 

定制错误处理逻辑

1、自定义错误页

(1)如果要显示给定状态代码的自定义 HTML 错误页面,可以将文件添加到 /error 文件夹

(2)错误页面可以是静态 HTML,即添加到任何静态资源文件夹下,也可以使用模板构建,在 /resources/templates 文件夹下

(3)文件名应该是确切的状态代码或系列掩码

(4)仍使用 DefaultErrorViewResolver 解析错误视图

(5)若存在精确错误状态码页面则匹配;若不存在模糊查询,如 4xx.html、5xx.html;若都没有就触发白页

2、@ControllerAdvice + @ExceptionHandler

(1)处理全局异常

(2)底层由 ExceptionHandlerExceptionResolver 处理异常

(3)@ExceptionHandler

public @interface ExceptionHandler {
    //处理方法被注解的异常。如果为空,将默认为方法参数列表中列出的任何异常
    Class<? extends Throwable>[] value() default {};

}

(4)示例

@ControllerAdvice
public class GlobalExceptionHandler {
    //捕获符合@ExceptionHandler中的异常
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
    public String handleArithException(Exception e){
        //处理异常逻辑
        return "exception";
    }
}

3、@ResponseStatus + 自定义异常

(1)底层是 ResponseStatusExceptionResolver 处理异常

(2)获取 @ResponseStatus 注解信息,由 Tomcat 发送 /error,若不能处理 /error,使用 Tomcat 默认错误页

//当前请求立即结束
response.sendError(statusCode, resolvedReason);

(3)@ResponseStatus

public @interface ResponseStatus {

    @AliasFor("code")
    HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

    @AliasFor("value")
    HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

    String reason() default "";
}

(4)示例

@ResponseStatus(value= HttpStatus.FORBIDDEN, reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {

    public  UserTooManyException(){

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

4、Spring 底层异常

(1)如:参数类型转换异常

(2)由 DefaultHandlerExceptionResolver 处理框架底层异常

(3)当前请求立刻结束,Tomcat 发送 /error,若不能处理 /error,使用 Tomcat 默认错误页

5、自定义异常解析器

(1)实现 HandlerExceptionResolver 接口

(2)使用 @Component 注册

(3)可以使用 @Order 调整优先级,数字越小,优先级越高,最高优先级为默认全局异常处理规则

6、自定义错误视图解析器(不使用)

(1)实现 ErrorViewResolver 接口

(2)异常解析器主动调用 response.sendError;或异常没有任何异常解析器能处理,Tomcat 底层调用 response.sendError

(3)error 请求转到 Controller,Spring 提供 BasicErrorController 处理 error 请求

posted @   半条咸鱼  阅读(58)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示