Error Handling
对于异常和错误我们一般希望给用户展示一个比较友好的页面,或者给调用者一个格式化得错误信息,springboot对于异常会有一个默认的‘whitelabel’ error view
页面,对于http rest客户端也会返回一个标准的json数据。现在我们要做的是定制自己的错误页和错误信息格式。
单个静态错误页
如果需求很简单,不需要针对不同的异常展示不同的页面,所有的异常和错误都对应到某一个定制的静态错误页,只需要将静态页放在特定的目录即可(src/main/resources/templates),springboot便会自动映射到该页面,这时访问一个不存在的链接便会返回这个错误页。值得注意的是有一些错误属性是可以在模板文件中直接使用的,比如status、message、path等。
单个动态错误页
可以通过实现ErrorViewResolver
接口向error页面添加一些动态数据
@Component
public class MyErrorViewResolver implements ErrorViewResolver {
public static final String DEFAULT_ERROR_VIEW = "error";
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView mav = new ModelAndView();
mav.addObject("status", status.value());
mav.addObject("message", status.getReasonPhrase());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
error.ftl代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>统一异常处理</title>
<body>
<h1>Error Handler</h1>
<div>status: ${status}</div>
<div>message: ${message}</div>
<div>url: ${url}</div>
</body>
</html>
这时启动随意访问一个不存在的网址便会看到
根据不同的情况返回不同的错误页
在 springboot 2.x 中实现方式如下,注意还有一种 EmbeddedServletContainerCustomizer 的方式只适用 springboot 1.x,现已经被废弃。
@Controller
public class GlobalErrorHandler implements ErrorPageRegistrar{
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
// ErrorPage error400Page = new ErrorPage(HttpStatus.BAD_REQUEST, "/error400Page");
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error401Page");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error404Page");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error500Page");
registry.addErrorPages(error401Page, error404Page, error500Page);
}
@RequestMapping(value = "/error401Page")
public String error400Page() {
return "error/noperm";
}
@RequestMapping(value = "/error404Page")
public String error404Page() {
return "error/notfound";
}
@RequestMapping(value = "/error500Page")
public String error500Page() {
return "error/server-error.ftl";
}
}
参考:https://www.cnblogs.com/wangzhuxing/p/10158390.html
针对不同的异常(或controller)返回不同的json数据
这是通过@ControllerAdvice
来实现的,定义如下一个类(注意可以不用继承ResponseEntityExceptionHandler),其中basePackageClasses
和ExceptionHandler
指定了该类会对指定的controller和指定的exception进行处理,By default the methods in an @ControllerAdvice apply globally to all Controllers。
@ControllerAdvice(basePackageClasses = HelloController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(RestException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, RestException rex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomerErrorType(rex.getErrorEnum().code, rex.getErrorEnum().message), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
这里便可以自定义CustomerErrorType
,例如
public class CustomerErrorType {
private Integer code;
private String message;
public CustomerErrorType(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
自定义异常类,
public class RestException extends Exception {
private static final long serialVersionUID = 1L;
private ErrorEnum errorEnum;
public RestException(ErrorEnum errorEnum) {
super();
this.setErrorEnum(errorEnum);
}
public ErrorEnum getErrorEnum() {
return errorEnum;
}
public void setErrorEnum(ErrorEnum errorEnum) {
this.errorEnum = errorEnum;
}
public enum ErrorEnum{
ERROR_SERVER(100, "server error"), ERROR_PARAMETERS(101, "parameters error");
public Integer code;
public String message;
private ErrorEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
}
这时再访问如下Controller方法
@RequestMapping("/json")
public String jsonException() throws RestException{
throw new RestException(ErrorEnum.ERROR_PARAMETERS);
}
返回结果如下:
{
"code": 101,
"message": "parameters error"
}
在同一个类中处理页面异常和rest请求异常
还是使用@ControllerAdvice类似于上面提到的方法,将page和rest请求异常处理放在同一个类里如下
@ControllerAdvice
public class GlobalExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
@ExceptionHandler(value = RestException.class)
@ResponseBody
public ResultWrap<String> defaultErrorJsonHandler(HttpServletRequest req, RestException e) throws Exception {
ResultWrap<String> errorInfo = new ResultWrap<String>();
errorInfo.setMessage(e.getErrorEnum().message);
errorInfo.setCode(e.getErrorEnum().code);
errorInfo.setDate("something is wrong!");
errorInfo.setUrl(req.getRequestURL().toString());
return errorInfo;
}
@ExceptionHandler(value = PageException.class)
public ModelAndView defaultErrorPageHandler(HttpServletRequest req, PageException e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("code", e.getErrorEnum().code);
mav.addObject("message", e.getErrorEnum().message);
mav.addObject("url", req.getRequestURL().toString());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
需要自定义业务异常,分别在page和rest请求中抛出不同的异常类型即可。当然容器中的异常还是需要通过上面提到的EmbeddedServletContainerCustomizer
或重写resolveErrorView
的方式实现。