springboot错误处理机制

本文以zb-shiro开源项目为例,使用thymeleaf模板引擎为例。

详解springboot错误处理机制原理:

springboot中错误处理自动配置类:ErrorMvcAutoConfiguration
其中主要注入四个组件:DefaultErrorAttributes,BasicErrorController,ErrorPageCustomizer,DefaultErrorViewResolver

1.DefaultErrorViewResolver

发生错误时,ErrorPageCustomizer生效(具体什么作用看下面对ErrorPageCustomizer的详解)。
然后交给/error处理(即BaseErrorController处理)。
响应页面由DefaultErrorViewResolver解析,/error/错误码。

2.ErrorPageCustomizer

系统出现错误时先走错误页面定制器:

public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
    ErrorPage errorPage = new ErrorPage(
            this.properties.getServlet().getServletPrefix()
                    + this.properties.getError().getPath());
    errorPageRegistry.addErrorPages(errorPage);
}

然后交给/error处理(等价于web.xml中的错误配置)

@Value("${error.path:/error}")
private String path = "/error";

3.BasicErrorController

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController

其中定义了两个错误处理方式(返回页面和json信息)

@RequestMapping(produces = "text/html")  //产生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));
}

@RequestMapping
# @ResponseBody  //产生json
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);
}

其中调用getErrorAttributes获取返回字段信息,点进去会发现是DefaultErrorAttributes。

protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
        boolean includeStackTrace) {
    WebRequest webRequest = new ServletWebRequest(request);
    return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}

4.DefaultErrorAttributes

public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
        boolean includeStackTrace) {
    Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
    errorAttributes.put("timestamp", new Date());
    addStatus(errorAttributes, requestAttributes);
    addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
    addPath(errorAttributes, requestAttributes);
    return errorAttributes;
}

定制错误响应

1.定制错误页面
1)有模板引擎:/error/状态码.html 页面。只需要在resources/error/目录下放对应的状态码.html即可。
状态码太多,还可以写4xx和5xx页面。(优先走精确状态码.html页面,然后匹配4xx或者5xx页面)。
错误页面路径
可以获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors
在模板引擎页面可以使用

<body>
    <h1>status:[[${status}]]</h1>
    <h2>timestamp:[[${timestamp}]]</h2>
</body>

2)没有模板引擎:在静态资源下找。
3)以上两点都没有,返回springboot默认错误页面。

2.定制错误json信息
看了上面springboot错误机制处理的原理,下面我们看如何自定义错误json信息。
1).自定义异常ZbException
2).自定义异常处理器ExceptionHandleController,处理ZbException

@ControllerAdvice
public class ExceptionHandleController {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionHandleController.class);

    @ExceptionHandler(ZbException.class)
    public String handleZb(Exception e, HttpServletRequest request) {
        request.setAttribute("javax.servlet.error.status_code",ResponseStatus.ERROR.getCode());  //传入错误状态码 ,比如500
        Map<String,Object> map = new HashMap<>(2);
        map.put("status", ResponseStatus.ERROR.getCode());
        map.put("msg", StringUtils.isNotBlank(e.getMessage())? e.getMessage() : ResponseStatus.ERROR.getMessage());
        logger.error(e.getMessage());
        request.setAttribute("ext",map);   //将我们自己要增加的json信息放入request中。
        return "forward:/error";    //转发给BaseErrorController处理即可。
    }

3).自定义MyErrorAttributes将request中我们放入的额外字段ext带进DefaultErrorAttributes

@Component
public class MyErrorAttributes extends DefaultErrorAttributes{

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String,Object> map  = super.getErrorAttributes(webRequest, includeStackTrace);
        Map<String,Object> ext = (Map<String,Object>)webRequest.getAttribute("ext",0);
        map.put("ext",ext);
        return map;
    }
}

大功告成!^_^