Spring Boot错误处理库包为REST API提供更好的错误处理 | foojay


好API 与坏API的区别是错误处理,Spring Boot 允许您自定义应用程序的错误处理,但是如果您想正确地执行此操作,则涉及很多低级编码。
构成良好的错误处理和良好的错误响应的最佳实践是什么?,这是可能有争议,但我认为我们可以就一些一般准则达成一致:

  • HTTP 响应代码应反映错误的性质(例如,对于未找到的内容返回 404,对于验证错误返回 400)
  • 响应正文应包含有关确切错误的更多信息。
  • 响应主体应该有一种代码,客户端可以在其中采取行动(例如USER_NOT_FOUND)
  • 对于验证问题,响应正文应指明字段名称,以便客户端可以例如突出显示存在验证问题的表单字段。

Spring Boot 的默认机制在这些方面做得并不好,因此Error Handling Spring Boot Starter库就派上用场了。
当您将库添加到 Spring Boot 应用程序时,它会自动注册一个控制器通知,该通知将为常见的 Spring 异常返回非常好的响应主体。
例如,这是针对@RestController方法的验证错误返回的内容:

{
  "code": "VALIDATION_FAILED",
 
"message": "Validation failed for object='exampleRequestBody'. Error count: 2",
 
"fieldErrors": [
    {
     
"code": "INVALID_SIZE",
     
"property": "name",
     
"message": "size must be between 10 and 2147483647",
     
"rejectedValue": ""
    },
    {
     
"code": "REQUIRED_NOT_BLANK",
     
"property": "favoriteMovie",
     
"message": "must not be blank",
     
"rejectedValue": null
    }
  ]
}

另一个例子是当ObjectOptimisticLockingFailureException发生时:
{
  "code": "OPTIMISTIC_LOCKING_ERROR",
 
"message": "Object of class [com.example.user.User] with identifier [87518c6b-1ba7-4757-a5d9-46e84c539f43]: optimistic locking failed",
 
"identifier": "87518c6b-1ba7-4757-a5d9-46e84c539f43",
 
"persistentClassName": "com.example.user.User"
}
 

自定义应用程序异常
对于您在自己的应用程序中创建的 Exception 类,库将code使用 Exception 类的名称生成错误。例如,如果您有UserNotFoundException,USER_NOT_FOUND则会生成错误代码。
在代码中,对于这样的异常类:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(UserId userId) {
        super("Could not find user with id " + userId);
    }
}

将返回以下 JSON:
{
  "code": "USER_NOT_FOUND",
 
"message": "Could not find user with id 123"
}

该库还遵循@ResponseStatus注释来确定所使用的 HTTP 响应代码。
可以通过以下几种方式自定义此基本行为:

  1. 通过覆盖错误代码 application.properties
  2. 通过覆盖错误代码 @ResponseErrorCode
  3. 在错误响应中添加额外的字段

 
通过属性覆盖错误代码
使用error.handling.codes异常类的键和全限定名,可以更改错误代码。例如:
error.handling.codes.com.company.app.user.UserNotFoundException=COULD_NOT_FIND_USER

应用它会将响应正文更改为如下所示:

{
  "code": "COULD_NOT_FIND_USER",
 
"message": "Could not find user with id 123"
}

如果您不拥有 Exception 类型,这可能是影响错误代码的唯一方法。如果您确实拥有 Exception 类型,那么使用@ResponseErrorCode注释可能更容易。
 
通过注解覆盖错误代码
通过@ResponseErrorCode在类级别添加注释,我们可以覆盖使用的错误代码。
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseErrorCode("NO_SUCH_USER")
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(UserId userId) {
        super(
"Could not find user with id " + userId);
    }
}

将生成以下响应:
{
  "code": "NO_SUCH_USER",
 
"message": "Could not find user with id 123"
}

 
在响应中的添加其他字段
如果您想在错误响应中添加其他字段,则可以通过在 Exception 类上使用@ErrorResponseProperty.
例如:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

    private final UserId userId;

    public UserNotFoundException(UserId userId) {
        super(String.format("Could not find user with id %s", userId));
        this.userId = userId;
    }

    @ResponseErrorProperty
    public String getUserId() {
        return userId.getValue();
    }
}

将生成以下响应:

{
  "code": "USER_NOT_FOUND",
 
"message": "Could not find user with id UserId{id=8c7fb13c-0924-47d4-821a-36f73558c898}",
 
"userId": "8c7fb13c-0924-47d4-821a-36f73558c898"
}

请注意userId响应中的额外字段。
 
测试
使用该库的优势之一也是测试支持。确切的同样的错误响应使用实际应用时,或使用一个完整的集成测试用时返回@SpringBootTest,或者使用Web测试片的@WebMvcTest。
当使用MockMvc+Error Handling Spring Boot Starter时,你能使用MockMvc测试错误处理,无需启动一个完整的@SpringBootTest.