优质博文: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得到ModelAndViewif(!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})@ConditionalOnMissingBeanpublic 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/404String errorViewName = "error/" + viewName;//模板引擎可以解析这个页面地址就用模板引擎解析TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);//模板引擎可用的情况下返回到errorViewName指定的视图地址,//当模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.htmlreturn 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{@Overridepublic 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】效果展示: