SpringBoot、SpringCloud、SpringMVC项目,使用tomcat容器,在404、500等错误之后,默认跳转到了BasicErrorController类的/error接口。
BasicErrorController.java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
// 省略其余方法
}
如果请求头里的accept如果是text/html,那么走errorHtml()方法。
如果不是,则走error()方法。
具体是在哪里设置跳转到${server.error.path:${error.path:/error}} 这个请求路径的,不同的容器不大一样。
如果是tomcat,那么是在StandardHostValve.java的status方法里设置的:
/**
* Handle the HTTP status code (and corresponding message) generated
* while processing the specified Request to produce the specified
* Response. Any exceptions that occur during generation of the error
* report are logged and swallowed.
*
* @param request The request being processed
* @param response The response being generated
*/
private void status(Request request, Response response) {
如果根据HTTP状态码(比如404)没有找到配置的错误页面,那么使用状态码0来查找配置的错误页面,默认值为/error。
设置完跳转信息之后,通过SpringMVC的DispatcherServlet的doDispatch方法来分发请求,从而将请求转发到了文章开头说到的BasicErrorController里。需要注意的是,转发前和转发后,使用的是同一个线程,可以使用线程局部变量ThreadLocal传递数据,但是最后一定要清除。
在实际项目中,可以写一个类来继承BasicErrorController,覆盖error方法,设置自定义的信息,或者直接实现ErrorController接口,比如:
@Controller
public class MyErrorController extends BasicErrorController {
private static Log log = LogFactory.getLog(MyErrorController.class);
public MyErrorController(ServerProperties serverProperties) {
super(new DefaultErrorAttributes(), serverProperties.getError());
}
/**
* 覆盖默认的Json响应
*/
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
// 获取原始的错误信息
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
Map<String, Object> result = new HashMap<>();
String code = null;
String message = null;
Object data = null;
// 设置自定义的错误信息,或者从ThreadLocal等获取错误信息
result.put("code", code);
result.put("message", message);
result.put("data", data);
cleanAllCache();
return new ResponseEntity<Map<String, Object>>(result, status);
}
}
说到异常,那么很有必要提到SpringBoot的全局异常处理,这个另起一篇文章吧。
说明:
ErrorController接口可以处理所有的异常,包括未进入控制器(controller)之前的异常
而注解@ControllerAdvice方式只能处理控制器抛出的异常。此时请求已经进入控制器中。因此无法拦截404、401等错误。
如果ErrorController和@ControllerAdvice同时存在,那么@ControllerAdvice优先处理,@ControllerAdvice处理不了的才会到ErrorController处理。
备注:
SpringBoot错误处理官方文档:Spring Boot Reference Documentation