SpringBoot默认的错误处理机制
-
原理
-
给容器中添加了一下组件
- DefaultErrorAttributes:帮我们分享页面信息
@Deprecated 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; }
- BasicErrorController:处理默认/error请求
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { @RequestMapping( produces = {"text/html"} )//产生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); } @RequestMapping //产生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.getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity(body, status); } }
- ErrorPageCustomizer:系统出现错误以后,来到error请求进行处理
@Value("${error.path:/error}") private String path = "/error";
-
DefaultErrorViewResolver
-
浏览器发送数据的请求头
-
其他客户端:accept:"/"
-
步骤
-
一旦系统出现4xx或5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则),来到/error请求
-
被BasicErrorController处理
-
响应页面,去哪个页面是由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(); //所有的ErrorViewResolver得到ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; }
-
DefaultErrorViewResolver如何解析
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) { //默认SpringBoot可以去找到一个页面。error/状态码 String errorViewName = "error/" + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); //模板引擎可用的情况下,返回errorViewName指定的视图地址 //模板引擎不可用的话,就在静态资源文件夹下找errorViewName对应的页面。error/状态码.html return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); } 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/状态码;【将错误页面命名为 错误状态码.html放在模板引擎文件夹下的error文件下】,发生此状态码的错误就会来到这个页面
- 我们可以使用4xx或者5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)
- 页面能获取的信息
- timestamp:时间戳’
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSP303数据校验的错误信息都在这里
- 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找
- 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
@Bean( name = {"error"} ) @ConditionalOnMissingBean( name = {"error"} ) public View defaultErrorView() { return this.defaultErrorView; }
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()); } }
自定义错误提示
<h1>status:[[${status}]]</h1> <h2>timestamp:[[${timestamp}]]</h2> <h2>error:[[${error}]]</h2> <h2>trace:[[${trace}]]</h2>
- 有模板引擎的情况下,error/状态码;【将错误页面命名为 错误状态码.html放在模板引擎文件夹下的error文件下】,发生此状态码的错误就会来到这个页面
-
定制错误的json数据
- 自定义异常处理,返回自定义json数据
//自定义异常 public class UserNotExistException extends RuntimeException { public UserNotExistException() { super("用户不存在"); } }
//抛出异常 @ResponseBody @RequestMapping("/hello") public String hello(@RequestParam("user") String user){ if (user.equals("aaa")){ throw new UserNotExistException(); } return "Hello Web"; }
//异常处理控制器,返回json数据 @ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(UserNotExistException.class) public Map<String, Object> handleException(Exception e){ Map<String, Object> map = new HashMap<>(); map.put("code","user,not exist"); map.put("message",e.getMessage()); return map; } }
- 没有自适应效果,浏览器和客户端返回的都是json
- 转发到/error,进行自适应响应效果处理
//异常处理器 @ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request) { Map<String, Object> map = new HashMap<>(); /** * 需要传入自己的错误状态码 * Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code"); */ request.setAttribute("javax.servlet.error.status_code",500); map.put("code", "user,not exist"); map.put("message", "用户数据错误"); //扩展异常属性 request.setAttribute("ext",map); return "forward:/error"; }
-
将我们的定制数据携带出去
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法)- 完全来编写一个ErrorController的实现类,或者编写AbstractErrorController的子类,放在容器中
- 页面上能用的数据,或者是json返回的数据都是通过errorAttributes.getErrorAttributes得到
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions options) { WebRequest webRequest = new ServletWebRequest(request); return this.errorAttributes.getErrorAttributes(webRequest, options); }
容器中DefaultErrorAttributes();默认进行数据处理的
@Bean @ConditionalOnMissingBean( value = {ErrorAttributes.class}, search = SearchStrategy.CURRENT ) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes @Component public class MyErrorAttributes extends DefaultErrorAttributes { //返回值的map就是页面喝json能获取的所有字段 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map<String, Object> map = super.getErrorAttributes(webRequest, options); map.put("company","whut"); //获取异常处理器携带的数据 Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);//request域中获取 map.put("ext",ext); return map; } }