错误处理和定制错误页面
配置原理:
可以参照ErrorMvcAutoConfiguration:错误处理的自动配置
给容器添加了如下组件:
DefaultErrorAttributes
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
BasicErrorController : 处理默认/error请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController{
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)//产生html类型的数据,浏览器发送的请求来到这个方法处理
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());
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);
}
ModelAndView modelAndView = 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 = 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);
}
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);
}
}
ErrorPageCustomizer
@Value("${error.path:/error}")
private String path="/error";//系统出现错误来到error请求进行处理,选择异常处理页面所在文件夹
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())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认springboot可以找到一个页面? error/404 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.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}catch (Exception ex) {
}
}
return null;
}
一旦发生错误,ErrorPageCustomizer就会发生作用,就会来到error下;就会被BasicErrorController 处理:
1)响应页面:
去哪个页面是由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())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; }
2)指定错误页面:
有模板引擎的情况下:根据error status状态码来到Templates文件下error文件夹中找对应的页面
没有的话,在static静态资源文件夹找
自定义异常处理:
@ResponseBody @org.springframework.web.bind.annotation.ExceptionHandler(NullUserException.class) public Map<String, String> handleNullUserException(Exception e) { Map<String, String> map = new HashMap<>(); map.put("name", "miao"); map.put("age", "23"); return map; }
没有自适应效果
@ResponseBody @org.springframework.web.bind.annotation.ExceptionHandler(NullUserException.class) public String handleNullUserException(Exception e, HttpServletRequest request) { // 一定要有自己的错误状态码 request.setAttribute("javax.servlet.error.status_code", 400); Map<String, String> map = new HashMap<>(); map.put("name", "miao"); map.put("age", "23"); return "forward:/error"; }
有自适应效果
定制自己的异常数据: 出现错误会后,会来到error,会被 BasicErrorControllrt 处理,响应出去的数据是由getErrorAttributes得到的 1,我们可以编写一个ErrorController的实现类,或者AbstractErrorController的子类,放在容器中
//@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
if ((Integer) map.get("status") == 500) {
map.put("message", "服务器内部错误!");
}
return map;
}
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
if ((Integer) map.get("status") == 500) {
map.put("message", "服务器内部错误!");
}
return map;
}
}
//定义好的 ErrorAttributes 一定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的 DefaultErrorAttributes 了
自定义异常视图解析
@Component public class MyErrorViewResolver extends DefaultErrorViewResolver { public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) { super(applicationContext, resourceProperties); } /** * */ @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { return new ModelAndView("/aaa/123", model); } }
实际上,开发者也可以在这里定义异常数据(直接在 resolveErrorView 方法重新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不可以直接修改),而不需要自定义MyErrorAttribute