SpringBoot——定制错误页面及原理
更多内容,前往 IT-BLOG
一、SpringBoot 默认的错误处理机制
【1】浏览器返回的默认错误页面如下:
二、原理分析
参照 ErrorMvcAutoConfiguration类:错误处理的自动配置类,以下4项为此类的重要信息。
【1】ErrorMvcAutoConfiguration.ErrorPageCustomizer:当系统出现 4xx或者 5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则),根据如下源码可知,将会来到 /error请求。
1 @Bean 2 public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() { 3 return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties); 4 } 5 6 //进入ErrorPageCustomizer方法,发现registerErrorPages方法:注册一个错误也 7 private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { 8 private final ServerProperties properties; 9 10 protected ErrorPageCustomizer(ServerProperties properties) { 11 this.properties = properties; 12 } 13 14 public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { 15 ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + 16 this.properties.getError().getPath()); 17 errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage}); 18 } 19 } 20 21 //进入this.properties.getError().getPath()方法,获取如下信息,得到/error请求。 22 @Value("${error.path:/error}") 23 private String path = "/error";//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
【2】BasicErrorController 处理 /error错误请求:注意:text/html 和 */*就是在此处生效。
1 @Bean 2 @ConditionalOnMissingBean( 3 value = {ErrorController.class}, 4 search = SearchStrategy.CURRENT 5 ) 6 public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { 7 return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); 8 } 9 10 //进入BasicErrorController对象,获取如下信息 11 @Controller 12 @RequestMapping({"${server.error.path:${error.path:/error}}"}) 13 public class BasicErrorController extends AbstractErrorController { 14 private final ErrorProperties errorProperties; 15 16 public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) { 17 this(errorAttributes, errorProperties, Collections.emptyList()); 18 } 19 20 @RequestMapping( 21 produces = {"text/html"}//产生html类型的数据;浏览器发送的请求来到这个方法处理 22 ) 23 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { 24 HttpStatus status = this.getStatus(request); 25 Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); 26 response.setStatus(status.value()); 27 28 //去哪个页面作为错误页面;包含页面地址和页面内容 29 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); 30 return modelAndView != null?modelAndView:new ModelAndView("error", model); 31 } 32 33 @RequestMapping 34 @ResponseBody//产生json数据,其他客户端来到这个方法处理; 35 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { 36 Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); 37 HttpStatus status = this.getStatus(request); 38 return new ResponseEntity(body, status); 39 } 40 }
☞ 如上代码中提到的错误页面解析代码,进入此方法: this.resolveErrorView(request, response, status, model);
1 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { 2 Iterator var5 = this.errorViewResolvers.iterator(); 3 ModelAndView modelAndView; 4 do { 5 //从所有的ErrorViewResolver得到ModelAndView 6 if(!var5.hasNext()) { 7 return null; 8 } 9 10 ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); 11 modelAndView = resolver.resolveErrorView(request, status, model); 12 } while(modelAndView == null); 13 14 return modelAndView; 15 }
【3】最终的响应页面是由 DefaultErrorViewResolver 解析得到的:最重要的信息是,SpringBoot 默认模板引擎的 /error目录下获取 ‘status’.xml 错误页面,也可以通过 4xx.xml来统配 404.xml和 400.xml等等,但是优先获取精准的页面。如果模板引擎中不存在,则会从静态页面中获取错误页面。否则返回系统默认错误页面。
1 @Bean 2 @ConditionalOnBean({DispatcherServlet.class}) 3 @ConditionalOnMissingBean 4 public DefaultErrorViewResolver conventionErrorViewResolver() { 5 return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); 6 } 7 8 //进入DefaultErrorViewResolver类中 9 public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { 10 ModelAndView modelAndView = this.resolve(String.valueOf(status), model); 11 if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { 12 13 //调用时viewname = status ***重要 14 modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); 15 } 16 17 return modelAndView; 18 } 19 20 private ModelAndView resolve(String viewName, Map<String, Object> model) { 21 22 //默认SpringBoot可以去找到一个页面? error/404 23 String errorViewName = "error/" + viewName; 24 25 //模板引擎可以解析这个页面地址就用模板引擎解析 26 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders. 27 getProvider(errorViewName, this.applicationContext); 28 29 //模板引擎可用的情况下返回到errorViewName指定的视图地址, 30 //当模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html 31 return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model); 32 }
【4】DefaultErrorAttributes:在页面添加错误信息,供我们使用。
1 @Bean 2 @ConditionalOnMissingBean( 3 value = {ErrorAttributes.class}, 4 search = SearchStrategy.CURRENT 5 ) 6 public DefaultErrorAttributes errorAttributes() { 7 return new DefaultErrorAttributes(); 8 } 9 10 //进入DefaultErrorAttributes类中,发现此方法给视图中添加了status状态等信息,供我们使用。 11 public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { 12 Map<String, Object> errorAttributes = new LinkedHashMap(); 13 errorAttributes.put("timestamp", new Date()); 14 this.addStatus(errorAttributes, requestAttributes); 15 this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); 16 this.addPath(errorAttributes, requestAttributes); 17 return errorAttributes; 18 }
三、定制错误 JSON数据
【1】自定义异常处理类,返回定制的 JSON数据。通过上述的分析,我们得知:①、可以完全编写一个 ErrorController的实现类,或者继承 AbstractErrorController的子类,放入容器中。②、也可以自定义异常处理类,返回 JSON数据。③、页面上的数据或 JSON返回的数据都是可以通过 errorAttributes.getErrorAttributes得到的。我们可以自定义属于自己的 ErrorAttributes。
1 //首先我们可以通过自定义异常处理,来确定返回的数据,但这个不够灵活,我们可以与③结合使用 2 /** 3 * @RequestMapping启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 4 * 注解的方法上。 5 */ 6 @ControllerAdvice 7 public class MyExceptionHandler { 8 @ResponseBody 9 @ExceptionHandler(UserNotExistException.class) 10 public Map<String,Object> handlerException(Exception e, HttpServletRequest request){ 11 Map<String,Object> map = new HashMap<String,Object>(); 12 request.setAttribute("javax.servlet.error.status_code","500"); 13 map.put("code","user.notexist"); 14 map.put("message",e.getMessage()); 15 return map; 16 } 17 } 18 19 //③自定义ErrorAttributes,一定要加入容器 20 @Component 21 public class MyErrorAttributes extends DefaultErrorAttributes{ 22 @Override 23 public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { 24 //获取默认的配置,在此基础上添加自己的需求 25 Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace); 26 //自定义自己需要的属性 27 map.put("company","yintong"); 28 29 //获取我们在异常处理类中添加的信息, 30 /*注意:当我们需要结合使用的时候异常处理必须return "forward:/error";将请求转发出去,不能直接返回map对象, 31 同时要去掉@responseBody注解,否则ErrorAttributes不生效*/ 32 map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST)); 33 return map; 34 } 35 }
【2】效果展示: