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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐