SpringBoot错误处理机制
1.SpringBoot默认的错误处理机制
默认效果:
-
返回一个默认的错误页面;
2.原理
参考:ErrorMvcAutoConfiguration;错误处理的自动配置。
给容器中添加了如下组件:
1.DefaultErrorAttributes:
是默认响应错误页面上的数据参数,可以通过继承该方法添加自己想添加的参数
2.BasicErrorController:处理默认的/error请求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
// 产生的是html类型的数据,浏览器发送的请求来到这个方法处理
@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);
}
//响应的是json数据,其他客户端来到这个方法处理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
3.ErrorPageCustomizer:定制错误的规则
@Value("${error.path:/error}")
private String path = "/error";系统出现错误以后来到error请求进行处理(类似web.xml注册的错误页面规则);
4.DefaultErrorViewResolver:通过模板引擎加载默认的错误页面
//方法1 解析错误页面
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
// 放法2 上面方法1要调用该方法
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//springboot默认可以去找到的一个页面?errer/404(状态码)
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用这个
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//模板可用调用ModelAndview这方法返回这个页面;不可用则调用resolveResource方法
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
//方法3 方法2中默认的模板引擎不可用,要调用该方法
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
// 要在静态资源文件夹下找errorViewName对应的页面
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
;
}
}
return null;
}
步骤:
一旦系统出现4xx或者是5xx之类的错误;
1.ErrorPageCustomizer就会生效(定制错误的相应规则);
2.就会来到/error请求;就会被BasicErrorController处理;
3.然后通过DefaultErrorViewResolver寻找响应的页面。
1)、响应页面:去哪个页面是由DefaultErrorViewResolver解析得到的:
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
3.如何订制错误响应:
1)、 如何定制错误页面(系统查找404页面的顺序)
)、有模板引擎的情况下;error/状态码(将错误页面命名为:状态码.html放在模板引擎文件夹里面的error文件夹下)发生此状态码的错误就来来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)
页面可以获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误
2)、没有模板引擎的情况下:(模板引擎找不到这个错误页面),静态资源文件下找;
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<>();
map.put("code","userNotExist");
map.put("message",e.getMessage());
return map;
}
2)、定制可以自适应的异常处理
// 将自适应交给springboot默认的去执行
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
// 传入我门自己的错误状态码
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","userNotExist");
map.put("message",e.getMessage());
return "forward:/error";
}
3)、将我们的定制数据携带出去
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以让获取到的数据是有getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
实现方法:(推荐使用第二种ErrorAttributes)
-
完全来编写一个ErrorController的实现类(或者是编写AbstractErrorController的子类),放在容器中。
-
页面上返回到的数据,或者json返回的。都是通过errorAttributes.getErrorAttributes得到的;容器中DefaultErrorAttributes.getErrorAttributes();默认进行处理,我们只需要自己写一个
@Bean // 条件判断是否有ErrorAttributes这个类,没有才调用DefaultErrorAttributes @ConditionalOnMissingBean( value = {ErrorAttributes.class}, search = SearchStrategy.CURRENT ) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException()); }
因此:我们可以自己实现一个ErrorAttributes类让其继承DefaultErrorAttributes类。
/**
* 容器中加入自定义的ErrorAttributes
*/
@Component
public class ErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.put("conpany","tit");
return errorAttributes;
}
}
客户端响应:
总结:
在开发过程中自定义一套错误处理:
1.使用模板引擎方式,添加自己的错误页面。
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"><div style="position: absolute; inset: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;" class="chartjs-size-monitor"><div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div></div><div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;"><div style="position:absolute;width:200%;height:200%;left:0; top:0"></div></div></div>
使用的是springboot默认的参数
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h3>exception:[[${exception}]]</h3>
使用的是MyExceptionHandler中自定义的参数
<h3>message:[[${message}]]</h3>
使用的是自己定制的ErrorAttributes的参数
<h2>conpany:[[${conpany}]]</h2>
使用的是MyExceptionHandler中自定义的参数
ext:[[${ext.message}]]
</main>
2.定制自己的异常错误类
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("用户不存在");
}
}
3.定制可以自适应的异常处理器
// 将自适应交给springboot默认的去执行(捕获自定义的异常类,不用让其出现在控制台)
@ControllerAdvice
public class MyExceptionHandler{
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
// 传入我门自己的错误状态码
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","userNotExist");
map.put("message",e.getMessage());
request.setAttribute("ext",map);
//重定向到springboot默认的异常解析器地址
return "forward:/error";
}
}
4.定制一个ErrorAttributes类让其继承DefaultErrorAttributes类改变需要返回的内容。
/**
* 容器中加入自定义的ErrorAttributes
*/
@Component
public class ErrorAttributes extends DefaultErrorAttributes {
// 返回值的map就是页面和json能获取的所有字段
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.put("conpany","tit");
// 将reqest域中的自己定制的异常处理器添加的数据也添加进来,0代表request域
Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
errorAttributes.put("ext",ext);
return errorAttributes;
}
}
最终结果:响应是自适应的。