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