------错误处理机制------
默认效果
1 返回一个默认的错误页面
浏览器发送请求的请求头:优先接收 text/html 数据
客户端则默认响应json数据 : accept 没有说明返回什么数据 默认返回json
原理:参照 ErrorMvcAutoConfiguration 错误的自动配置
1 DefaultErrorAttributes
// 帮助我们共享页面信息
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; }
2 BasicErrorController
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) // 都是响应 /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.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 去哪个页面作为错误页面 包含页面地址和内容
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
}
@RequestMapping
@ResponseBody // 产生json数据
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
3 ErrorPageCustomizer
@Value("${error.path:/error}") private String path = "/error"; 系统出现错误 到 error请求 (web.xml 注册的错误规则)
4 DefaultErrorViewResolver
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = this.resolve(String.valueOf(status), 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) {
// 默认可以找到一个页面 error/404 String errorViewName = "error/" + viewName;
// 模板引擎可以解析这个页面地址就用模板引擎解析 // 模板引擎可以用的情况下返回得到errorViewName指定视图地址
// 模板引擎不可用 就在静态资源文件夹下找 errorViewName 对应的页面
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); }
步骤:
一旦系统出现4xx或者5xx错误:ErrorPageCustomizer 生效(定制错误的响应规则) 就会来到/error 请求
就会被 BasicErrorController 处理:
1) 响应页面: 去哪个页面由 DefaultErrorViewResolver 决定
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); // 所有的ErrorViewResolver 得到 ModelAndView ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; }
2 如何定制错误响应
1) 定制错误页面
1) 有模板引擎的情况下:error/ 状态码 ( error/404.html) 将错误页面命名为错误状态码.html 发生此状态码到此错误页面
也可以使用 4xx.html或者5xx.html 来匹配 4开头的或者5开头的错误 如果有精确的会优先到精确的错误提示页面
页面能获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303 数据校验的错误信息
2) 没有模板引擎,在静态资源文件夹下寻找 static文件夹下寻找
3) 模板引擎静态资源都没有 就是默认来到springboot 默认的错误提示页面
2) 定制json 错误数据
1) 自定义异常处理&返回json数据
//异常处理器 @ControllerAdvice public class MyExceptionHandler {
// 浏览器客户端返回都是json数据 @ResponseBody @ExceptionHandler(UserNotExistException.class) public Map<String , Object> handleException(Exception e) { Map<String , Object> map = new HashMap<String, Object>(); map.put("code", "user.notexist"); map.put("message", e.getMessage()); return map; } }
// 没有自适应效果...
2) 转发到/error 进行自适应响应 实现功能但是 map.put 信息无法接受
@ResponseBody @ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request) { Map<String , Object> map = new HashMap<String, Object>(); // 传入我们自己设置的状态码 4xx 5xx 否则不会进入到错误页面的解析流程 request.setAttribute("javax.servlet.error.status_code", 400); map.put("code", "user.notexist"); map.put("message", e.getMessage()); // 转发到 /error页面 return "forward:/error"; }
3) 将我们的定制数据携带出去
出现错误以后 会来到 /error 请求 会被 ErrorBaseController 处理,响应出去的数据可以
获取的数据时由 getErrorAttributes 得到的(是 AbstractErrorController规定的方法)
---1完全来编写一个 ErrorController【或者编写AbstractErrorController类子类】 的实现类放在容器中
---2页面上能用的数据 或者json 能用的返回数据 通过 errorAttributes.getErrorAttributes得到
容器中DefaultErrorAttributes.getErrorAttributes 此方法定义返回数据可以自己写一个
// 给容器加入自己定义的ErrorAttributes @Component 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("company", "lixuchun"); // scope // request 0 // session 1 Map<String , Object> ext = (Map<String , Object>)webRequest.getAttribute("ext", 0); map.put("ext", ext); return map; } }
在MyExceptionHandler 中加入
@ResponseBody @ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request) { Map<String , Object> map = new HashMap<String, Object>(); // 传入我们自己设置的状态码 4xx 5xx 否则不会进入到错误页面的解析流程 request.setAttribute("javax.servlet.error.status_code", 400); map.put("code", "user.notexist"); map.put("message", e.getMessage()); request.setAttribute("ext", map); // 转发到 /error页面 return "forward:/error"; }
访问结果: