原文: https://howtodoinjava.com/spring-boot2/spring-rest-request-validation/

在本SpringBoot 异常处理器教程中,我们将学习验证发送到 PUT / POST REST API 的请求正文。 我们还将学习在验证错误的 API 响应中添加自定义错误消息。

在这个 SpringBoot 示例中,我们将主要看到两个主要的验证案例:

  1. HTTP POST /employees和请求正文不包含有效值,或者某些字段丢失。 它将在响应正文中返回 HTTP 状态代码 400,并带有正确的消息。
  2. HTTP GET /employees/{id}和无效 ID 在请求中发送。 它将在响应正文中返回带有正确消息的 HTTP 状态代码 404。

阅读更多: HTTP 状态代码

1. 创建 REST API 和模型类

给定的 REST API 来自员工管理模块。

EmployeeRESTController.java

  1. @PostMapping(value = "/employees")
  2. public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee)
  3. {
  4. EmployeeDB.addEmployee(employee);
  5. return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
  6. }
  7. @GetMapping(value = "/employees/{id}")
  8. public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id)
  9. {
  10. EmployeeVO employee = EmployeeDB.getEmployeeById(id);
  11. if(employee == null) {
  12. throw new RecordNotFoundException("Invalid employee id : " + id);
  13. }
  14. return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
  15. }

EmployeeVO.java

  1. @XmlRootElement(name = "employee")
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. public class EmployeeVO extends ResourceSupport implements Serializable
  4. {
  5. private Integer employeeId;
  6. private String firstName;
  7. private String lastName;
  8. private String email;
  9. public EmployeeVO(Integer id, String firstName, String lastName, String email) {
  10. super();
  11. this.employeeId = id;
  12. this.firstName = firstName;
  13. this.lastName = lastName;
  14. this.email = email;
  15. }
  16. public EmployeeVO() {
  17. }
  18. //Removed setter/getter for readability
  19. }

2. Spring Boot 异常处理 – REST 请求验证

2.1. 默认的 Spring 验证支持

要应用默认验证,我们只需要在适当的位置添加相关注解即可。 即

  1. 使用所需的验证特定注解(例如@NotEmpty@Email等)注解模型类

    1. @XmlRootElement(name = "employee")
    2. @XmlAccessorType(XmlAccessType.FIELD)
    3. public class EmployeeVO extends ResourceSupport implements Serializable
    4. {
    5. private static final long serialVersionUID = 1L;
    6. public EmployeeVO(Integer id, String firstName, String lastName, String email) {
    7. super();
    8. this.employeeId = id;
    9. this.firstName = firstName;
    10. this.lastName = lastName;
    11. this.email = email;
    12. }
    13. public EmployeeVO() {
    14. }
    15. private Integer employeeId;
    16. @NotEmpty(message = "first name must not be empty")
    17. private String firstName;
    18. @NotEmpty(message = "last name must not be empty")
    19. private String lastName;
    20. @NotEmpty(message = "email must not be empty")
    21. @Email(message = "email should be a valid email")
    22. private String email;
    23. //Removed setter/getter for readability
    24. }
  1. 通过@Valid注解启用验证请求正文
    1. @PostMapping(value = "/employees")
    2. public ResponseEntity<EmployeeVO> addEmployee (@Valid @RequestBody EmployeeVO employee)
    3. {
    4. EmployeeDB.addEmployee(employee);
    5. return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
    6. }

2.2. 异常模型类

默认的 Spring 验证有效并提供有关错误的信息过多,这就是为什么我们应根据应用程序的需要对其进行自定义。 我们将仅提供措辞非常明确的必要错误信息。 不建议提供其他信息。

创建有意义的异常并充分描述问题始终是一个很好的建议。 一种方法是创建单独的类来表示特定的业务用例失败,并在该用例失败时返回它们。

阅读更多: Java 异常处理 – 新的应用程序

例如我为所有通过 ID 要求资源但在系统中找不到资源的场景创建了RecordNotFoundException类。

RecordNotFoundException.java

  1. package com.howtodoinjava.demo.exception;
  2. import org.springframework.http.HttpStatus;
  3. import org.springframework.web.bind.annotation.ResponseStatus;
  4. @ResponseStatus(HttpStatus.NOT_FOUND)
  5. public class RecordNotFoundException extends RuntimeException
  6. {
  7. public RecordNotFoundException(String exception) {
  8. super(exception);
  9. }
  10. }

同样,我编写了一个特殊的类,将为所有失败情况返回该类。 所有 API 具有一致的错误消息结构,可帮助 API 使用者编写更健壮的代码。

ErrorResponse.java

  1. import java.util.List;
  2. import javax.xml.bind.annotation.XmlRootElement;
  3. @XmlRootElement(name = "error")
  4. public class ErrorResponse
  5. {
  6. public ErrorResponse(String message, List<String> details) {
  7. super();
  8. this.message = message;
  9. this.details = details;
  10. }
  11. //General error message about nature of error
  12. private String message;
  13. //Specific errors in API request processing
  14. private List<String> details;
  15. //Getter and setters
  16. }

2.3. 自定义ExceptionHandler

现在添加一个扩展ResponseEntityExceptionHandler的类,并使用@ControllerAdvice注解对其进行注解。

ResponseEntityExceptionHandler是一个方便的基类,用于通过@ExceptionHandler方法跨所有@RequestMapping方法提供集中式异常处理。 @ControllerAdvice更多用于在应用程序启动时启用自动扫描和配置。

用于@ControllerAdvice异常处理示例的 Java 程序。

CustomExceptionHandler.java

  1. package com.howtodoinjava.demo.exception;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.springframework.http.HttpHeaders;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.http.ResponseEntity;
  7. import org.springframework.validation.ObjectError;
  8. import org.springframework.web.bind.MethodArgumentNotValidException;
  9. import org.springframework.web.bind.annotation.ControllerAdvice;
  10. import org.springframework.web.bind.annotation.ExceptionHandler;
  11. import org.springframework.web.context.request.WebRequest;
  12. import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
  13. @SuppressWarnings({"unchecked","rawtypes"})
  14. @ControllerAdvice
  15. public class CustomExceptionHandler extends ResponseEntityExceptionHandler
  16. {
  17. @ExceptionHandler(Exception.class)
  18. public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
  19. List<String> details = new ArrayList<>();
  20. details.add(ex.getLocalizedMessage());
  21. ErrorResponse error = new ErrorResponse("Server Error", details);
  22. return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
  23. }
  24. @ExceptionHandler(RecordNotFoundException.class)
  25. public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
  26. List<String> details = new ArrayList<>();
  27. details.add(ex.getLocalizedMessage());
  28. ErrorResponse error = new ErrorResponse("Record Not Found", details);
  29. return new ResponseEntity(error, HttpStatus.NOT_FOUND);
  30. }
  31. @Override
  32. protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
  33. List<String> details = new ArrayList<>();
  34. for(ObjectError error : ex.getBindingResult().getAllErrors()) {
  35. details.add(error.getDefaultMessage());
  36. }
  37. ErrorResponse error = new ErrorResponse("Validation Failed", details);
  38. return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
  39. }
  40. }

上面的类处理多个异常,包括RecordNotFoundException; 并且还可以处理@RequestBody带注解的对象中的请求验证错误。 让我们看看它是如何工作的。

3. Spring Boot 异常处理 – 演示

1)HTTP GET /employees/1 [有效]

  1. HTTP Status : 200
  2. {
  3. "employeeId": 1,
  4. "firstName": "John",
  5. "lastName": "Wick",
  6. "email": "howtodoinjava@gmail.com",
  7. }

2)HTTP GET /employees/23 [无效]

  1. HTTP Status : 404
  2. {
  3. "message": "Record Not Found",
  4. "details": [
  5. "Invalid employee id : 23"
  6. ]
  7. }

3)HTTP POST /employees [无效]

Request

  1. {
  2. "lastName": "Bill",
  3. "email": "ibill@gmail.com"
  4. }

Response

  1. HTTP Status : 400
  2. {
  3. "message": "Validation Failed",
  4. "details": [
  5. "first name must not be empty"
  6. ]
  7. }

4)HTTP POST /employees [无效]

Request

  1. {
  2. "email": "ibill@gmail.com"
  3. }

Response

  1. HTTP Status : 400
  2. {
  3. "message": "Validation Failed",
  4. "details": [
  5. "last name must not be empty",
  6. "first name must not be empty"
  7. ]
  8. }

5)HTTP POST /employees [无效]

Request

  1. {
  2. "firstName":"Lokesh",
  3. "email": "ibill_gmail.com" //invalid email in request
  4. }

Response

  1. HTTP Status : 400
  2. {
  3. "message": "Validation Failed",
  4. "details": [
  5. "last name must not be empty",
  6. "email should be a valid email"
  7. ]
  8. }

4. REST 请求验证注解

在上面的示例中,我们仅使用了很少的注解,例如@NotEmpty@Email。 还有更多此类注解可用于验证请求数据。 必要时将其签出。

注解 用法
@AssertFalse 带注解的元素必须为false
@AssertTrue 带注解的元素必须为true
@DecimalMax 带注解的元素必须是一个数字,其值必须小于或等于指定的最大值。
@DecimalMin 带注解的元素必须是一个数字,其值必须大于或等于指定的最小值。
@Future 带注解的元素必须是将来的瞬间,日期或时间。
@Max 带注解的元素必须是一个数字,其值必须小于或等于指定的最大值。
@Min 带注解的元素必须是一个数字,其值必须大于或等于指定的最小值。
@Negative 带注解的元素必须是严格的负数。
@NotBlank 带注解的元素不能为null,并且必须至少包含一个非空白字符。
@NotEmpty 带注解的元素不能为null,也不能为空。
@NotNull 带注解的元素不能为null
@Null 带注解的元素必须为null
@Pattern 带注解的CharSequence必须与指定的正则表达式匹配。
@Positive 带注解的元素必须是严格的正数。
@Size 带注解的元素大小必须在指定的边界(包括在内)之间。

5. 总结

在此Spring REST 验证教程中,我们学习了:

  • 通过 ID 提取资源时验证 ID。
  • 验证 POST / PUT API 中的请求正文字段。
  • 在 API 响应中发送一致且结构化的错误响应。

将有关 spring rest 异常处理的问题交给我。

学习愉快!

参考:javax.validation.constraints