Spring Boot 异常处理

Spring Boot 异常处理

Spring Boot处理异常的时候有两种办法: 根据状态码创建对应错误页面 自定义错误类型返回具体JSON 在编写REST风格的API的时候,如果通过浏览器访问不存在的地址,会得到一个HTML格式的错误。如果用非浏览器比如POSTMAN朝不存在的地址发一个响应,得到的是一个: { "tim

Spring Boot处理异常的时候有两种办法:
  1. 根据状态码创建对应错误页面
  2. 自定义错误类型返回具体JSON
在编写REST风格的API的时候,如果通过浏览器访问不存在的地址,会得到一个HTML格式的错误。如果用非浏览器比如POSTMAN朝不存在的地址发一个响应,得到的是一个:
{
    "timestamp": "2019-06-01T11:09:59.938+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/userf"
}
包含错误信息的JSON对象。这是怎么做到的呢。

同一个路径根据不同响应头返回不同内容

Spring 的错误处理的一个基础类叫做BasicErrorController,简要代码如下:
package org.springframework.boot.autoconfigure.web.servlet.error;

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

    @RequestMapping(
    produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }


    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
}
这个类实际上也是一个控制器,可以看到类的标注,响应/error这个路径。 这里有两个RequestMapping,一个说明了请求头里如果有Accept:text/html,就会返回后边的errorHtml对象。如果没有,就返回一个Map对象转换成的JSON对象。 这里实际上是要学习,如何使用同一个路径针对不同的请求头作出不同的响应。在Spring Security相关的开发中使用的比较广泛。

Spring Boot框架的默认错误处理

在POST请求中使用了BindingResult来捕捉错误。如果没有捕捉,直接POST一个密码为空的JSON过去,(关闭认证和CSRF),能看到错误的JSON字符串:
{
    "timestamp": "2019-06-01T12:07:53.277+0000",
    "status": 400,
    "error": "Bad Request",
    "errors":[
        {
            "codes":["NotBlank.user.password", "NotBlank.password", "NotBlank.java.lang.String", "NotBlank"],
            "arguments":[{"codes":["user.password", "password" ], "arguments": null, "defaultMessage": "password",…],
            "defaultMessage": "密码不能为空白",
            "objectName": "user",
            "field": "password",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 1",
    "path": "/user"
}
这实际上是框架捕捉了响应的错误信息,然后组装成了JSON进行返回。实际上这个异常被框架拦截了,说明框架有一个异常拦截器。 如果我们自己的控制器里要捕捉异常怎么做呢。 在GET方法里自己抛一个运行时错误:
@GetMapping("/{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User queryDetail(@PathVariable String id) {

    throw new RuntimeException("故意抛出的错误");
}
用网页访问还可以看到错误页面,用REST访问也能拿到JSON,可以看到,前端只要了解了Spring Boot默认的错误机制,也可以处理错误。

自定义错误页面

Spring Boot会自动到resources/resources/error/下边寻找与错误码名称相同的HTML页面,比如404.html,作为浏览器出错的时候对应的返回页面。 而用REST访问的时候,该返回错误对象依然是错误对象。 这是一个比较简单的通过默认HTML页面处理错误处理。

自定义错误类来控制REST返回的JSON内容

要自定义的话,先要自定义一个错误类型:
package com.imooc.security.exhandler;

public class UserNotExistException extends RuntimeException {

    private static final long serialVersionUID = -21378921789984L;

    public UserNotExistException(String id) {
        super("用户不存在,自定义的错误类, id是 " + id);
        this.id = id;
    }


    //自定义的部分
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}
我们想返回id的内容,如果此时只是抛出自定义错误,Spring Boot打包之后的错误信息里不会包含id的内容,必须自己编写一个处理错误的类:
package com.imooc.security.exhandler;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

//这个注解表示该控制器用于处理其他控制器中出现的异常
@ControllerAdvice
public class ControllerExceptionHandler {


    //这个注解用于处理对应的异常,这里就是针对UserNotExistException异常
    @ExceptionHandler(UserNotExistException.class)
    //加上了注解之后,这个方法的参数就是对应的异常对象
    // @ResponseBody 表示将Controller返回的结果写入响应体,而不是像普通视图一样进行视图解析
    //一般用在这种特殊的控制类中,因为不能直接标记上RestController
    @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;
    }
}
使用了@ControllerAdvice及对应注解之后,遇到这种错误就会返回此种JSON,而不是Spring Boot默认的JSON。 这样在编写REST客户端的时候,就可以灵活的定义错误,可以给前端传递更多的信息,这样就方便获取具体的内容。 如果愿意的话,在后端验证出现错误的时候,也可以抛出一个自定义的异常,把所有异常的字段和错误信息都返回给前端用于处理。
LICENSED UNDER CC BY-NC-SA 4.0
Comment