前言
利用@ControllerAdvice和@ExceptionHandler进行全局异常处理的时候智能处理应用级别的异常,容器级别异常是处理不了的,例如filter中抛出异常。
在Spring boot中,默认情况下,如果用户发起请求时出现了404错误,Spring boot 会提供一个默认的页面展示给用户。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB90MZRkTyUEROBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwEDN5EDNxQTM4ATMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
Spring boot在返回错误信息的时候,不一定返回html页面,而是根据实际情况返回HTML页面或者是一段json。
1 Spring boot默认配置
spring boot默认是由 BasicErrorController类来处理的,该类有两个核心方法
@RequestMapping( produces = {"text/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 ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
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);
}
errorHtml 返回 错误HTML页面
error 返回错误JSON
注意
返回html还是json,就要看请求头的Accept参数。
errorHtml方法 ==》 resolveErrorView方法 获取一个错误视图 ModelAndView
resolveErrorView方法属于 DefaultErrorViewResolver 类(Spring Boot 默认的错误信息视图解析器)
private static final Map<Series, String> SERIES_VIEWS;
// 初始化 SERIES_VIEWS
static {
Map<Series, String> views = new EnumMap(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
// 产生一个只读的map
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
// viewName 其实是httpStatus
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 视图定义在error 文件夹下
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
从上述源码可以看出 Spring boot默认是在error目录下查找4xx、5xx的文件作为错误视图(需继续验证),如果找不到的话就会回到errorHtml方法中,使用error作为默认的错误页面视图名(如果配置了视图层,就会去templates下找error.html)。
2 默认实现
2.1 静态页面
不需要向用户展示详细的错误信息,那么就可以把错误信息定义成静态页面,直接在resources/static/error 创建错误展示页面。
命名规则
- 4xx.html 、5xx.html
- 使用响应码命名文件
2.2 动态页面
可以动态的展示更多的错误信息。在/resources/templates/error/目录下创建错误展示页。4xx.html 、5xx.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>错误页</title>
</head>
<body>
<table border="1">
<tr>
<td>custommsg</td>
<td th:text="${custommsg}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
</table>
</body>
</html>
后台会自动包装 timestamp , status , error , message , path 这五个属性,errorHtml 或者 error 方法中都是通过getErrorAttributes 方法获得这五个属性的。
响应吗html页面优先级 高于 4xx.html
动态页面的优先级高于静态页面
3 自定义error数据
在上一节里,了解到Spring boot返回的Error信息一共有5条信息,都是通过getErrorAttributes 方法获得的。最终会调用到中的此方法
DefaultErrorAttributes类 ⇒ getErrorAttributes 方法
DefaultErrorAttributes类 ⇒ ErrorMvcAutoConfiguration 类如果没有就会创建
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
自定义数据只需要自己编写一个ErrorAttributes类。继承DefaultErrorAttributes即可。
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String,Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
errorAttributes.put("custommsg","出错啦");
//errorAttributes.remove("error");
return errorAttributes;
}
}
4 自定义Error视图
ErrorMvcAutoConfiguration 不仅创建了DefaultErrorAttributes类,还创建了DefaultErrorViewResolver 类
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
自定义视图就要提供自定义的ErrorViewResolver
@Component
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView mv = new ModelAndView("errorPage");
mv.addObject("custommsg","出错了");
mv.addAllObjects(model);
return mv;
}
}
接着前台进行错误展示的视图就应该变成自定义的errorPage。无论发生的是4xx错误还是5xx错误都会进入errorPage.html页面。
5 完全自定义
前面的自定义方式都是针对BasicErrorController中error方法和errorHtml方法中替换ErrorViewResolver(视图),ErrorAttributes(数据)来实现自定义。但其实BasicErrorController 也是由ErrorMvcAutoConfiguration 默认生成的。
@Bean
@ConditionalOnMissingBean(
value = {ErrorController.class},
search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
完全自定义需要提供自定义的Errorcontroller , 一种是实现Errorcontroller接口,另一种是直接继承BasicErrorController类。因为BasicErrorController的功能更加完善,所以选择继承BasicErrorController。
@Controller
public class MyErrorController extends BasicErrorController {
@Autowired
public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//状态码
HttpStatus status = getStatus(request);
// 得到基本的五个 error 信息
Map<String,Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
model.put("custommsg","出错了");
ModelAndView modelAndView = new ModelAndView("myErrorPage",model,status);
return modelAndView;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String,Object> body = getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
body.put("custommsg","出错啦");
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body,status);
}
}
摘抄自 《spring boot 全栈开发实战》部分自己理解