SpringBoot覆盖默认的错误处理方式
覆盖默认的错误处理方式
默认错误处理机制的响应内容格式不一定是你相中的。理由可能如下:
Whitelabel Error Page页面的样式太单调,用户体验不好。Json 格式的结果字符串不统一,与你配合的前端人员更希望统一格式,好做统一的显示处理。比如与前端人员配合时统一指定响应结果格式为
{status:true,msg:”xxx”,data:{xxx}},但 Spring Boot 的 Json 格式是{status:500,message:”error occur”,path:”/exception”……}。
那么你可能更期望可以修改默认的处理方式,改变响应内容格式。Spring Boot 开发指南上给出了几种方法。
自定义一个bean,实现 ErrorController 接口,那么默认的错误处理机制将不再生效。
自定义一个 bean,继承 BasicErrorController 类,使用一部分现成的功能,自己也可以添加新的public方法,使用
@RequestMapping及其 produces 属性指定新的地址映射。自定义一个 ErrorAttribute 类型的 bean,那么还是默认的两种响应方式,只不过改变了内容项而已。
继承 AbstractErrorController。
BasicErrorController源码
SpringBoot 在页面 发生异常的时候会自动把请求转到 /error,SpringBoot 内置了一个 BasicErrorController 对异常进行统一的处理,当然也可以自定义这个路径。
server:port: 8080error:path: /custom/error
BasicErrorController 提供两种返回错误一种是页面返回、当你是页面请求的时候就会返回页面,另外一种是 json 请求的时候就会返回 json 错误:
@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 ? new ModelAndView("error", model) : modelAndView);}@RequestMapping@ResponseBodypublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.ALL));HttpStatus status = getStatus(request);return new ResponseEntity<Map<String, Object>>(body, status);}
分别会有如下两种返回

自定义一个bean,继承BasicErrorController类
BasicErrorController,这个是 SpringBoot 的默认错误处理,也是一种全局处理方式。咱们可以模仿这种处理方式自定义自己的全局错误处理 下面定义了一个自己的 BasicErrorController,可以根据自己的需求自定义 errorHtml() 和 error() 的返回值。
SpringBoot提供了一种特殊的Bean定义方式,可以让我们容易的覆盖已经定义好的Controller,原生的BasicErrorController是定义在ErrorMvcAutoConfiguration中的 具体代码如下:
@Bean@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {return new BasicErrorController(errorAttributes, this.serverProperties.getError(),this.errorViewResolvers);}
可以看到这个注解 @ConditionalOnMissingBean 意思就是定义这个 bean 当 ErrorController.class 这个没有定义的时候, 意思就是说只要我们在代码里面定义了自己的ErrorController.class 时,这段代码就不生效了。
自定义 MyErrorController:
package com.lf.exception;import org.springframework.boot.autoconfigure.web.BasicErrorController;import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;import org.springframework.boot.autoconfigure.web.ServerProperties;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Component;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.HashMap;import java.util.Map;/*** Created by LF on 2017/4/16.*/@Controllerpublic class MyErrorController extends BasicErrorController {public MyErrorController(ServerProperties serverProperties) {super(new DefaultErrorAttributes(), serverProperties.getError());}/*** 覆盖默认的Json响应*/@Overridepublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.ALL));HttpStatus status = getStatus(request);//输出自定义的Json格式Map<String, Object> map = new HashMap<String, Object>();map.put("status", false);map.put("msg", body.get("message"));return new ResponseEntity<Map<String, Object>>(map, status);}/*** 覆盖默认的HTML响应*/@Overridepublic ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {//请求的状态HttpStatus status = getStatus(request);response.setStatus(getStatus(request).value());Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));ModelAndView modelAndView = resolveErrorView(request, response, status, model);//指定自定义的视图return(modelAndView == null ? new ModelAndView("error", model) : modelAndView);}}
总结
其实查看 BasicErrorController 源码,响应结果不论是 html 还是 json,内容源都是 ErrorAttribute。第三种方法只能改变内容,却改变不了格式,特别是 html 页面的样式,笔者就不予考虑了。
无论第一种还是第二种办法,都需要你看下BasicErrorController的继承体系及实现,因为 BasicErrorController 也是实现了 ErrorController。
采用第一种方式你可以具有完全的控制权,你可以摒弃默认的 “Whitelabel Error Page”,指定自己的视图及视图样式,你可以指定响应的Json格式内容等等,因为 BasicErrorController 不再起作用。
参照了 BasicErrorController 的源码,感觉第二种方法可能更简便些,所以实现了第二种方法。第二种方法的思路其实就是你可以通过继承,利用 BasicErrorController 已有的功能,或者进行扩展。
评论