Spring boot的默认异常处理机制
我们以访问一个不存在的页面的场景为例,结果是返回一个错误页面:
而我们一个前后端分离的架构,我们写的Restful API往往会被多个渠道访问,比如浏览器,app。而我们的spring boo会根据不同的渠道做出不同的响应,是浏览器发的就返回html,不是则是json。若报错回跳转到/error的URL,同一个URL不同的处理方式是由Spring boot提供的BasicErrorController错误控制器实现的。
// 如果请求头中accept有text/html这个值
@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);
}
当我们对请求的参数进行校验,当校验不通过时,spring boot会返回一个400状态码,并且并把我们所有的错误信息放进一个error里面来告诉我们客户端哪些字段有问题,问题是什么。
{
"timestamp": 1584868482621,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"NotBlank.user.password",
"NotBlank.password",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"user.password",
"password"
],
"arguments": null,
"defaultMessage": "password",
"code": "password"
}
],
"defaultMessage": "密码不能为空",
"objectName": "user",
"field": "password",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='user'. Error count: 1",
"path": "/user/1"
}
一般如果是在我们的服务里面报的错误,Spring boot默认会响应给浏览器的是一个状态码的500的服务器错误。
我们用postman模拟其他的渠道,返回的是:
{
"timestamp": 1584869332592,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "user not exist",
"path": "/user/1"
}
虽然由spring boot提供的默认处理机制可以很好的处理我们的异常。但有时候,我们也要去自定义异常处理来满足我们特定的需求场景。那么该如何自定义异常处理的呢?
下面我们来实现404和500跳转到我们指定的页面,在我们的resources目录下创建我们相应的的html文件。注意文件目录和文件名要固定。
浏览器访问结果是跳转到我们特定的页面,
这种方式是安装spring boot的约束来自定义的,他仅能适用于浏览器的请求,而对app返回的任然是json。
有些时候,我们的想要的异常返回结果还需要有其他的错误信息。下面我来实现用户不存在的异常。并且返回的异常包含不存在的用户得id。
public class UserNotExistException extends RuntimeException {
private static final long serialVersionUID = -6112780192479692859L;
private String id;
public UserNotExistException(String id) {
super("user not exist");
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
我们在服务里面抛出这个异常
@GetMapping("{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User getUserInfo(@PathVariable(name = "id") String id){
throw new UserNotExistException(id);
}
用我们postman访问:
{
"timestamp": 1584870909493,
"status": 500,
"error": "Internal Server Error",
"exception": "com.zhaohong.exception.UserNotExistException",
"message": "user not exist",
"path": "/user/1"
}
我们可以看到异常变了,是UserNotExistException,由于默认情况下,返回的异常不会去读message之外的信息,所以返回的结果中并没有包含用户的id,那么如何把这个id返回给前台。
下面我们自定义一个异常处理器。
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, Object> handleUserNotExistException(UserNotExistException ex) {
Map<String, Object> result = new HashMap<>();
result.put("id", ex.getId());
result.put("message", ex.getMessage());
return result;
}
}
@ExceptionHandler指定了要捕获的异常,@ResponseStatus指定返回的状态码。
我们再次反问服务,结果是返回:
{
"id": "user not exist",
"message": "user not exist"
}