1、Springboot的错误页面到底是怎么生辰的?
首先看一下springboot默认的错误处理机制
默认效果:
1)、浏览器,返回一个默认的错误页面
2)、如果是其他客户端,默认响应一个json数据
其他端发送的请求的请求头:
注意:上面的不同的浏览器访问结果不一样,一个是json数据,而另一个是错误页面,请关注上面的标注的两个红色的框,一个是accept=text/html ,另一个是accept=*/*,下面有对此的解释,请往下看:
说到Springboot的mvc的处理机制那我们必须要想到springboot的自动配置的错误!!!!
原理如下: 可以参照ErrorMvcAutoConfiguration(这个自动配置很重要!!!);错误处理的自动配置;
ErrorMvcAutoConfiguration 给容器中添加了以下组件:
1、ErrorPageCustomizer
@Value("${error.path:/error}")
private String path = "/error";
系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
当系统出现4XX或者5XX的时候,
ErrorPageCustomizer就会生效(定制错误的响应规则)
|
@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); } 只有当容器里面没有ErrorController这个类的时候才会注入BasicErrorController |
|
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}") //
这个就是要处理的错误机制的controller
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = 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 = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
|
这个就能解释上面那个不同的浏览器访问会出现不同结果的原因。 如果要自己定义一些信息,而且要到达不同浏览器返回不同的效果,可以把请求转发到这个Controller来处理。 例如:return:"forward:/error"就可以了 |
3、DefaultErrorViewResolver
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } |
|
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), 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对应的页面 error/404.html
return resolveResource(errorViewName, model);
}
|
这个类主要的作用就是解析视图: 当没有模板的情况下,就会在根目录下寻找:error/状态码.html页面,没有的话就返回默认的白板页面; 有模板也和上面差不多,只是在Templates目下找(Thymeleaf) |
4、DefaultErrorAttributes
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes( this.serverProperties.getError().isIncludeException()); } DefaultErrorAttributes是ErrorAttributes的实现类 这个类里面定义了返回页面的(或者json)的数据信息。 |
|
帮我们在页面共享信息; @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); //时间戳 addStatus(errorAttributes, requestAttributes);//错误状态码 addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; } 提供的数据有: timestamp:时间戳 status:状态码 error:错误提示 exception:异常对象 message:异常消息 errors:JSR303数据校验的错误都在这里 如果用到了模板的话可以获取这些信息的,列如thymeleaf获取的方式: <h1>status:[[${status}]]</h1> |
BasicErrorController处理;
1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //所有的ErrorViewResolver得到ModelAndView for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; } |
private final SpelView defaultErrorView = new SpelView( "<html><body><h1>Whitelabel Error Page</h1>" + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>" + "<div id='created'>${timestamp}</div>" + "<div>There was an unexpected error (type=${error}, status=${status}).</div>" + "<div>${message}</div></body></html>"); @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } |
1)、如何定制错误的页面;
1)、有模板引擎的情况下;error/状态码;** 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
公有的:
下面是自定义的代码:
public class UserException extends RuntimeException{ public UserException() { super("用户不存在"); } } |
@RequestMapping("exception") public String toException(){ throw new UserException(); } |
1)、自定义异常处理&返回定制json数据;
@ControllerAdvice public class UserControllerDdvice { @ResponseBody @ExceptionHandler(UserException.class) public Map<String, Object> handler(Exception e){ Map<String, Object> map =new HashMap<>(); Ext etx=new Ext("ext_有错误","etc_code"); map.put("username", "abinuu"); map.put("error", "UserException"); map.put("ext", etx); map.put("message", e.getMessage()); return map; } } 下面就是返回的数据(任何访问方式都是返回下面的json) {"ext":{"message":"ext_有错误","code":"etc_code"},"error":"UserException","message":"用户不存在","username":"abinuu"} |
2)、转发到/error进行自适应响应效果处理
@ExceptionHandler(UserException.class) public String handler(Exception e,HttpServletRequest request){ Map<String, Object> map =new HashMap<>(); Ext etx=new Ext("ext_有错误","etc_code"); map.put("username", "abinuu"); map.put("error", "UserException"); map.put("ext", etx); map.put("message", e.getMessage()); //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程 /** * Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); */ request.setAttribute("map", map);//这个在下面3)的时候会用到 return "forward:/error";//转发到BasicErrorController } |
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
* 这个一定要交给容器管理才起作用的,默认的错误机制的DefaultErrorAttributes就不会注册了,而是会 * 注入我们的DefaultErrorAttributuesMyself */ @Component public class DefaultErrorAttributuesMyself extends DefaultErrorAttributes{ @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);//这个是继承了DefaultErrorAttributes的所有信息,下面在往map加的数据就是自己定义的信息了 map.put("company", "jrj");//这个就是自己定义的信息 } 在上面的2)里面一点要把信息存到request.setAttribute("map", map); |