好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 响应代码。
可以通过以下几种方式自定义此基本行为:
- 通过覆盖错误代码 application.properties
- 通过覆盖错误代码 @ResponseErrorCode
- 在错误响应中添加额外的字段
通过属性覆盖错误代码
使用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.