基础13-错误处理

MVC下的错误处理核心类是ErrorMvcAutoConfiguration类;

这里先看一下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

ErrorMvcAutoConfiguration类里面有下面几个重要的类:

①DefaultErrorAttributes类:

public DefaultErrorAttributes errorAttributes() {

        return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());

}

②BasicErrorController类:

public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {

        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));

    }

③DefaultErrorViewResolver类:

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

        }

    }

DefaultErrorAttributes类

 DefaultErrorAttributes(this.serverProperties.
getError().isIncludeException());该类是通过ServerProperties的ErrorProperties来创建的;而在ErrorProperties中path指定了显示异常的文件路径(默认为/error目录),ErrorProperties配置的属性都是以server.error开头:

public class ErrorProperties {

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

    private String path = "/error";

    private boolean includeException;

    private ErrorProperties.IncludeStacktrace includeStacktrace;

    private final ErrorProperties.Whitelabel whitelabel;

而ServerProperties对应的属性前缀都是server开头的:

在创建DefaultErrorAttributes对象时,includeException属性为false;

通过查看该类的resolveException、storeErrorAttributes、getErrorAttributes、addStatus、addErrorDetails、addErrorMessage、addStackTrace、addPath等方法,我们会发现该类的主要功能是:向WebRequest对象中添加timestamp时间戳、status状态码、error错误提示、exception异常对象、message异常消息、errors(JSR303数据校验错误)、tracepath属性;

下面是该类的源码:

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";

    private final boolean includeException;

 

    public DefaultErrorAttributes() {

        this(false);

    }

 

    public DefaultErrorAttributes(boolean includeException) {

        this.includeException = includeException;

    }

 

    public int getOrder() {

        return -2147483648;

    }

 

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

        this.storeErrorAttributes(request, ex);

        return null;

    }

 

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {

        request.setAttribute(ERROR_ATTRIBUTE, ex);

    }

 

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {

        Map<String, Object> errorAttributes = new LinkedHashMap();

        errorAttributes.put("timestamp", new Date());

        this.addStatus(errorAttributes, webRequest);

        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);

        this.addPath(errorAttributes, webRequest);

        return errorAttributes;

    }

 

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {

        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");

        if (status == null) {

            errorAttributes.put("status", 999);

            errorAttributes.put("error", "None");

        } else {

            errorAttributes.put("status", status);

 

            try {

                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());

            } catch (Exception var5) {

                errorAttributes.put("error", "Http Status " + status);

            }

 

        }

    }

 

    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {

        Throwable error = this.getError(webRequest);

        if (error != null) {

            while(true) {

                if (!(error instanceof ServletException) || error.getCause() == null) {

                    if (this.includeException) {

                        errorAttributes.put("exception", error.getClass().getName());

                    }

 

                    this.addErrorMessage(errorAttributes, error);

                    if (includeStackTrace) {

                        this.addStackTrace(errorAttributes, error);

                    }

                    break;

                }

 

                error = error.getCause();

            }

        }

 

        Object message = this.getAttribute(webRequest, "javax.servlet.error.message");

        if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {

            errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);

        }

 

    }

 

    private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {

        BindingResult result = this.extractBindingResult(error);

        if (result == null) {

            errorAttributes.put("message", error.getMessage());

        } else {

            if (result.hasErrors()) {

                errorAttributes.put("errors", result.getAllErrors());

                errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());

            } else {

                errorAttributes.put("message", "No errors");

            }

 

        }

    }

 

    private BindingResult extractBindingResult(Throwable error) {

        if (error instanceof BindingResult) {

            return (BindingResult)error;

        } else {

            return error instanceof MethodArgumentNotValidException ? ((MethodArgumentNotValidException)error).getBindingResult() : 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 = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");

        if (path != null) {

            errorAttributes.put("path", path);

        }

 

    }

 

    public Throwable getError(WebRequest webRequest) {

        Throwable exception = (Throwable)this.getAttribute(webRequest, ERROR_ATTRIBUTE);

        if (exception == null) {

            exception = (Throwable)this.getAttribute(webRequest, "javax.servlet.error.exception");

        }

 

        return exception;

    }

 

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {

        return requestAttributes.getAttribute(name, 0);

    }

}

BasicErrorController与DefaultErrorViewResolver类

跟踪该类的构造器,我们发现在其父类AbstractErrorController构造器时初始化了ErrorView的视图解析器List<ErrorViewResolver> errorViewResolvers;

这里重点查看下BasicErrorController.errorHtml方法:

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

        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);

        return modelAndView != null ? modelAndView : new ModelAndView("error", model);

    }

通过resolveErrorView方法来返回视图模型,如果有则返回该视图模型,否则返回一个名字叫做“error”视图模型;

继续跟踪resolveErrorView方法源码:

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;

    }

这里遍历了errorViewResolvers变量(参考第一自然段),然后转成ErrorViewResolver接口,接着我们查看该接口的实现类DefaultErrorViewResolver的resolveErrorView方法:

 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;

}

 

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

这里,如果找不到对应的status.html文件,则默认就把status的第1位数字与SEIES_VIEWS来匹配,直接使用4xx.html或者5xx.html文件;

继续跟踪resolve方法:

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

}

这里继续跟踪resolveResource方法:

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {

        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");

                if (resource.exists()) {

                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);

                }

            } catch (Exception var8) {

            }

        }

        return null;

    }

我们可以得出如下结论:

①默认情况下去找/error/status.html文件;

②获取对应的Provider,如果有,则直接返回;

③如果没有对应的Provider,则去分别遍历如下目录classpath:/META-INF/resources/、classpath:/resources/、 classpath:/static/、classpath:/public/,去查找名叫status.html的文件:

总结,Spring Boot异常处理流程:

①有模板引擎的情况下,首先查找/error/status.html文件(将错误页面命名为:错误状态码.html,然后放到/templates/error文件夹下),当发生此状态码的错误时,Spring Boot就会找到该错误码.html文件;

②没有模板引擎的情况下,再去classpath:/META-INF/resources/classpath:/resources/ classpath:/static/classpath:/public/查找status.html文件;

注意:这里的静态资源无法解析,只能原样展示HTML文件,也无法解析timestamp时间戳、status状态码、error错误提示、exception异常对象、message异常消息、errors(JSR303数据校验错误)、tracepath这些属性;

③如果还是没有找到对应的status.html,则Spring Boot根据状态码第1位,来用4xx和5xx来作为错误页面的文件名来匹配这种类型的所有错误;

④如果连4xx和5xx都匹配不上,则只能使用Spring Boot已经定义好的error视图;

public 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();

Date timestamp = (Date) 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());

}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值