springboot的错误处理自动配置类是ErrorMvcAutoConfiguration,我们请求发生错误时,如果是浏览器请求则返回html页面,如果是客户端请求则返回JSON数据,下面我们来看看是如何处理,类ErrorMvcAutoConfiguration代码如下
public class ErrorMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
@Configuration
static class DefaultErrorViewResolverConfiguration {
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
}
}
这里给容器创建了四个重要的错误处理bean,DefaultErrorAttributes,BasicErrorController,ErrorPageCustomizer,DefaultErrorViewResolver。
方法getErrorAttributes返回的Map里包含了错误请求返回的数据,包括时间戳,状态码,错误消息,异常信息等,如果我们想自定义自己的错误信息就可以重写这个方法
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered {
@Override
public Map getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
},
默认处理/error请求,浏览器和客户端请求的区别就是看request请求头header里有没有accept对象,如果有就表示是浏览器请求
@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 = getStatus(request);
Map model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
如果不存在返回的视图就返回默认的error视图
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
客户端请求返回JSON数据
@RequestMapping
@ResponseBody
public ResponseEntity
这个类是寻找错误页面,默认到/error请求,如果没有配置页面路径的话,默认到类路径下error目录下寻找,通过状态码进行匹配获取
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
方法getPath就是寻找error目录下的错误页面
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}
}
@Value("${error.path:/error}")
private String path = "/error";
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
static {
Map views = new HashMap();
添加状态码寻找界面时进行匹配
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map model) {
通过状态码去寻找html
ModelAndView modelAndView = resolve(String.valueOf(status), model);
html不存在的话则使用通配符4XX或5XX进行匹配
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
直接到error目录下寻找页面,如果存在的话直接返回视图
private ModelAndView resolve(String viewName, Map model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
不存在的话就继续寻找
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map model) {
error目录下不存在匹配的界面就到静态资源static下去寻找html
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;
}
1.自定义返回界面的话非常简单,只需要在类路径的templates下建立error文件夹,里面放对应的错误页面即可,或者可以使用一个通用的错误页面,4xx.html,也可以在静态资源目录static下建立error文件夹,错误处理类都可以找得到
2.自定义错误信息
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
@Override
public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("name", requestAttributes.getAttribute("name", 0));
return map;
}
}
我们需要自定义JSON数据只需要继承DefaultErrorAttributes重写获取错误信息方法就行了,可以放入自定义的信息内容
3.自适应错误返回页面还是JSON数据
我们在上面已经解释过默认请求的处理是/error,它会自己去判断当前请求是浏览器发送还是客户端发送,所以我们的异常处理只需要将错误信息转发到/error请求,就可以自适应返回页面还是JSON了
@ControllerAdvice 声明这个类是异常处理类必须使用@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(MyException.class)声明方法处理哪个异常使用注解@ExceptionHandler
public String handleException(Exception e, HttpServletRequest request){
//传入我们自己的错误状态码 4xx 5xx
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("name","芜湖最靓的仔");
//转发到/error
return "forward:/error";
}
}