覆盖默认的错误处理方式
默认错误处理机制的响应内容格式不一定是你相中的。理由可能如下:
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: 8080
error:
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
@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<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.
*/
@Controller
public class MyErrorController extends BasicErrorController {
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);
//输出自定义的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响应
*/
@Override
public 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 已有的功能,或者进行扩展。