Springboot错误处理机制


浏览器访问不存在资源效果
在这里插入图片描述

一、底层分析

1.ErrorPageCustomizer 错误页面定制器

public class ErrorMvcAutoConfiguration {
	public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
				// this.properties.getError().getPath() = /error
	            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
	            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
	        }
}

当应用出现了4xx或5xx之类的错误 ,ErrorPageCustomizer就会被激活,它主要 用于定制错误 处理的响应规则,就会发送一个/error请求,它会交给 BasicErrorController进行处理

2.BasicErrorController 就会接收 /error 请求处理


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

     //通过请求头判断调用下面哪个访求: text/html
	@RequestMapping(
        produces = {"text/html"}
    )
    //接受error请求,然后响应错误页面
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //去哪个页面作为错误页面,包括 页面地址与页面内容,里面有一个ErrorViewResolver 
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        //没有找到,则找 error 视图 ,在ErrorMvcAutoConfiguration的defaultErrorView中
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    //接受error请求,然后响应json数据
    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.isIncludeStackTrace(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
}

3.DefaultErrorViewResolver 解析响应错误页面

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
    	//根据状态码查找对应的页面
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        //匹配4xx、5xx页面
        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) {
    	//error目录下的页面
        String errorViewName = "error/" + viewName; 
        //先查找模板引擎下的页面
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //如果目标引擎有该页面,则返回指定视图,否则去静态资源目录下找对应错误码的页面
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    	//获取四个静态资源目录"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
        String[] var3 = this.resourceProperties.getStaticLocations();
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];

            try {
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");//如:404.html
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
            }
        }

        return null;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    static {
    	//定义4xx、5xx页面
        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);
    }
}

4.DefaultErrorAttributes 错误页面可获取到的数据信息
通过 BasicErrorController 的方法中响应的 module 可定位到响应哪些数据,从而引出 ErrorAttributes 的实现类 DefaultErrorAttributes , DefaultErrorAttributes 中绑定的所有值都可在页面获取到。

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        //this.getErrorAttributes 调用了父类的方法
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }
}

BasicErrorController父类:AbstractErrorController

public abstract class AbstractErrorController implements ErrorController {
    private final ErrorAttributes errorAttributes;
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }
}

ErrorAttributes接口的实现类:DefaultErrorAttributes

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        //添加时间戳到map "timestamp"
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }
}

可获取数据的字段:

  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验出现的错

模板引擎错误页面可以获取这些数据

5. 模板目录与静态目录下都找不到对应错误页面,就响应 SpringBoot 默认的错误页面
通过 BasicErrorController 的 errorhtml 方法后 一行可知,没有找到则找 error 视图对象 ,error定义 在 ErrorMvcAutoConfiguration 的 defaultErrorView 中
ErrorMvcAutoConfiguration类

    protected static class WhitelabelErrorViewConfiguration {
    	//ErrorMvcAutoConfiguration.StaticView拼接错误信息
        private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
        protected WhitelabelErrorViewConfiguration() {
        }

ErrorMvcAutoConfiguration.StaticView内部类,拼接错误信息

private static class StaticView implements View {
        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();
                Date timestamp = (Date)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());
            }
        }

二、自定义数据进行响应

出现错误以后,会发送 /error 请求,会被 BasicErrorController 处理,而响应的数据是DefaultErrorAttributes.getErrorAttributes 封装的, 所以我们只需要自定义 ErrorAttributes 实现类即可
1.自定义一个类继承DefaultErrorAttributes

public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //保留之前的数据
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //添加自定义的数据
        map.put("test", "测试数据........");
        return map;
    }
}

2.错误页面获取数据测试

<body>    
	404页面
	<p>[[${timestamp}]]</p>
	<p>[[${test}]]</p> 
</body>

三、自定义错误拦截器

异常处理类

@ControllerAdvice //拦截所有标注@Controller的控制器
public class ControllerExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    //该注解表示这个方法可以异常处理,拦截的异常为Exception或Exception的子异常
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e){
        //输出错误信息
        logger.error("Request URL:{}, Exception: {}",request.getRequestURL(),e);

        ModelAndView mv = new ModelAndView();
        mv.addObject("url",request.getRequestURL());
        mv.addObject("exception",e);
        //转发到前端error页面
        mv.setViewName("error/error");
        return mv;
    }
}

错误页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>error</title>
</head>
<body>
    <h3>Error</h3>
    <div>
        <!--th:utext:转义字符串,th:remove移除标签,以注释的方式输出字符串-->
        <div th:utext="'&lt;!--'" th:remove="tag"></div>
        <div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag"></div>
        <div th:utext="'Exception message : ' + ${exception.message}" th:remove="tag"></div>
        <ul th:remove="tag">
            <li th:each="st : ${exception.stackTrace}" th:remove="tag"><span th:utext="${st}" th:remove="tag"></span></li>
        </ul>
        <div th:utext="'--&gt;'" th:remove="tag"></div>
    </div>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值