Validate Boolean Type in Spring Boot – 在 Spring Boot 中验证布尔类型

最后修改: 2023年 10月 17日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

In this tutorial, we’ll learn how to validate the Boolean type in a Spring Boot application and look at various ways to perform the validation. Furthermore, we’ll validate an object of type Boolean at various Spring Boot application layers, such as the controller or service layer.

在本教程中,我们将学习如何在 Spring Boot 应用程序中验证 Boolean 类型,并了解执行验证的各种方法。此外,我们还将在不同的 Spring Boot 应用程序层(如控制器层或服务层)验证 Boolean 类型的对象。

2. Programmatic Validation

2.计划验证

The Boolean class provides two essential methods to create an instance of the class: Boolean.valueOf() and Boolean.parseBoolean().

Boolean 类提供了两个创建该类实例的基本方法:Boolean.valueOf()Boolean.parseBoolean().

Boolean.valueOf() accepts both String and boolean values. It checks if the value of the input field is true or false and accordingly provides a Boolean object. The Boolean.parseBoolean() method only accepts string values.

Boolean.valueOf() 同时接受 Stringboolean 值。它会检查输入字段的值是 true 还是 false,并相应地提供一个 Boolean 对象。Boolean.parseBoolean() 方法只接受字符串值。

These methods are not case-sensitive — for example, “true”, “True”, “TRUE”, “false”, “False”, and “FALSE” are acceptable inputs.

这些方法不区分大小写,例如,”true”、”True”、”TRUE”、”false “和 “FALSE “都是可接受的输入。

Let’s validate the String to Boolean conversions with a unit test:

让我们通过单元测试来验证 StringBoolean 的转换:

@Test
void givenInputAsString_whenStringToBoolean_thenValidBooleanConversion() {
    assertEquals(Boolean.TRUE, Boolean.valueOf("TRUE"));
    assertEquals(Boolean.FALSE, Boolean.valueOf("false"));
    assertEquals(Boolean.TRUE, Boolean.parseBoolean("True"));
}

We’ll now validate the conversion from the primitive boolean value to the Boolean wrapper class:

现在,我们将验证从原始 布尔值 布尔封装类的转换:

@Test
void givenInputAsboolean_whenbooleanToBoolean_thenValidBooleanConversion() {
    assertEquals(Boolean.TRUE, Boolean.valueOf(true));
    assertEquals(Boolean.FALSE, Boolean.valueOf(false));
}

3. Validation With Custom Jackson Deserializer

3.使用自定义 Jackson 反序列化器进行验证

As Spring Boot APIs often handle JSON data, we’ll also look at how we can validate the JSON to Boolean conversion via data deserialization. We can deserialize custom representations of boolean values using a custom deserializer.

由于 Spring Boot API 经常处理 JSON 数据,我们还将了解如何通过数据反序列化验证 JSON 到 Boolean 的转换。我们可以使用自定义反序列化器反序列化布尔值的自定义表示。

Let’s consider a scenario where we want to consume JSON data that represents a boolean value with + (for true) and (for false). Let’s write a JSON deserializer to achieve this:

让我们考虑这样一种情况:我们希望使用 JSON 数据,该数据用 +(表示 true)和 (表示 false)表示布尔值。让我们编写一个 JSON 反序列化器来实现这一目标:

public class BooleanDeserializer extends JsonDeserializer<Boolean> {
    @Override
    public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String value = parser.getText();
        if (value != null && value.equals("+")) {
            return Boolean.TRUE;
        } else if (value != null && value.equals("-")) {
            return Boolean.FALSE;
        } else {
            throw new IllegalArgumentException("Only values accepted as Boolean are + and -");
        }
    }
}

4. Bean Validation With Annotations

4.使用注解进行 Bean 验证

Bean validation constraints are another popular approach to validate fields. To use this, we need spring-boot-starter-validation dependency. Out of all available validation annotations, three of them can be used for a Boolean field:

Bean 验证约束是验证字段的另一种常用方法。要使用这种方法,我们需要 spring-boot-starter-validation 依赖关系。在所有可用的验证注解中,有三个注解可用于 Boolean 字段:

  • @NotNull: Produces an error if the Boolean field is null
  • @AssertTrue: Produces an error if the Boolean field is set to false
  • @AssertFalse: Produces an error if the Boolean field is set to true

It’s important to note that both @AssertTrue and @AssertFalse consider null values as valid input. That means, if we want to make sure that only actual boolean values are accepted, we need to use these two annotations in combination with @NotNull.

需要注意的是,@AssertTrue@AssertFalse 都将 null 值视为有效输入。这意味着,如果我们要确保只接受实际的布尔值,就需要将这两个注解与 @NotNull 结合使用。

5. Example of Boolean Validation

5.布尔验证示例

To demonstrate this, we’ll use both bean constraints and a custom JSON deserializer at the controller and service layers. Let’s create a custom object named BooleanObject with four parameters of Boolean type. Each of them will use a different validation approach:

为了演示这一点,我们将在控制器层和服务层使用 Bean 约束和自定义 JSON 反序列化器。让我们创建一个名为 BooleanObject 的自定义对象,该对象具有四个 Boolean 类型的参数。每个参数都将使用不同的验证方法:

public class BooleanObject {

    @NotNull(message = "boolField cannot be null")
    Boolean boolField;

    @AssertTrue(message = "trueField must have true value")
    Boolean trueField;

    @NotNull(message = "falseField cannot be null")
    @AssertFalse(message = "falseField must have false value")
    Boolean falseField;

    @JsonDeserialize(using = BooleanDeserializer.class)
    Boolean boolStringVar;

    //getters and setters
}

6. Validation in the Controller

6.控制器中的验证

Whenever we pass an object via a RequestBody to a REST endpoint, we can use the @Valid annotation to validate the object. When we apply the @Valid annotation to a method parameter, we instruct Spring to validate the respective user object:

每当我们通过 RequestBody 向 REST 端点传递对象时,我们都可以使用 @Valid 注解来验证该对象。当我们将 @Valid 注解应用于方法参数时,我们将指示 Spring 验证相应的用户对象

@RestController
public class ValidationController {

    @Autowired
    ValidationService service;

    @PostMapping("/validateBoolean")
    public ResponseEntity<String> processBooleanObject(@RequestBody @Valid BooleanObject booleanObj) {
        return ResponseEntity.ok("BooleanObject is valid");
    }

    @PostMapping("/validateBooleanAtService")
    public ResponseEntity<String> processBooleanObjectAtService() {
        BooleanObject boolObj = new BooleanObject();
        boolObj.setBoolField(Boolean.TRUE);
        boolObj.setTrueField(Boolean.FALSE);
        service.processBoolean(boolObj);
        return ResponseEntity.ok("BooleanObject is valid");
    }
}

After the validation, if any violation is found, Spring will throw MethodArgumentNotValidException. To handle this, ControllerAdvice with the relevant ExceptionHandler methods can be used. Let’s create three methods to handle respective exceptions thrown from the Controller and Service layer:

验证完成后,如果发现任何违规行为,Spring 将抛出 MethodArgumentNotValidException 异常。要处理这种情况,可以使用带有相关 ExceptionHandler 方法的 ControllerAdvice 方法。让我们创建三个方法来处理控制器和服务层分别抛出的异常:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleValidationException(MethodArgumentNotValidException ex) {
        return ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getDefaultMessage())
            .collect(Collectors.joining(","));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleIllegalArugmentException(IllegalArgumentException ex) {
        return ex.getMessage();
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleConstraintViolationException(ConstraintViolationException ex) {
       return ex.getMessage();
    }
}

Before testing the REST functionality, we recommend going through testing APIs in Spring Boot. Let’s create the structure of the test class for the Controller:

在测试 REST 功能之前,我们建议您了解在 Spring Boot 中测试 API 。让我们创建控制器测试类的结构:

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = ValidationController.class)
class ValidationControllerUnitTest {

    @Autowired
    private MockMvc mockMvc;

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public ValidationService validationService() {
            return new ValidationService() {};
        }
    }

    @Autowired
    ValidationService service;
}

With this in place, we can now test the validation annotations we’ve used in our class.

有了这些,我们现在就可以测试我们在类中使用的验证注解了。

6.1. Validate @NotNull Annotation

6.1.验证 @NotNull 注释

Let’s look at how the @NotNull works. When we pass the BooleanObject with a null Boolean parameter, @Valid annotation will validate the bean and throw a “400 Bad Request” HTTP response:

让我们来看看 @NotNull 是如何工作的。当我们传递带有 null Boolean 参数的 BooleanObject 时,@Valid注解将验证 bean 并抛出 “400 Bad Request”(400 坏请求)HTTP 响应:

@Test
void whenNullInputForBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception {
    String postBody = "{\"boolField\":null,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
            .content(postBody))
        .andExpect(status().isBadRequest());
}

6.2. Validate @AssertTrue Annotation

6.2.验证 @AssertTrue 注释

Next, we’ll test the working of @AssertTrue. When we pass the BooleanObject with a false Boolean parameter, @Valid annotation will validate the bean and throw a “400 Bad Request” HTTP response. If we capture the response body, we can get the error message set inside the @AssertTrue annotation:

接下来,我们将测试 @AssertTrue 的工作情况。当我们传递带有 false Boolean 参数的 BooleanObject 时,@Valid注解将验证 bean 并抛出 “400 Bad Request”(400 坏请求)HTTP 响应。如果我们捕获响应体,就可以获得 @AssertTrue 注解中设置的错误信息:

 @Test
 void whenInvalidInputForTrueBooleanField_thenErrorResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":false,\"falseField\":false,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("trueField must have true value", output);
 }

Let’s also check what happens if we provide null. Since we annotated the field only with @AssertTrue, but not with @NotNull, there’s no validation error:

让我们再看看如果我们提供 null 会发生什么情况。由于我们只用 @AssertTrue 注解了字段,而没有用 @NotNull 注解,因此不会出现验证错误:

 @Test
 void whenNullInputForTrueBooleanField_thenCorrectResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":null,\"falseField\":false,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
                    .content(postBody))
            .andExpect(status().isOk());
}

6.3. Validate @AssertFalse Annotation

6.3.验证 @AssertFalse 注释

We’ll now understand the working of @AssertFalse. When we pass a true value for the @AssertFalse parameter, the @Valid annotation throws a bad request. We can get the error message set against the @AssertFalse annotation in the response body:

现在我们将了解 @AssertFalse 的工作原理。当我们为 @AssertFalse 参数传递一个 true 值时,@Valid 注解会抛出一个错误请求。我们可以在响应体中获取针对 @AssertFalse 注解设置的错误信息:

 @Test
 void whenInvalidInputForFalseBooleanField_thenErrorResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":true,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("falseField must have false value", output);
 }

Again, let’s see what happens if we provide null. We annotated the field with both @AssertFalse and @NotNull, therefore, we’ll get a validation error:

同样,让我们看看如果我们提供 null 会发生什么。我们同时用 @AssertFalse @NotNull 对字段进行了注释,因此会出现验证错误:

 @Test
 void whenNullInputForFalseBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":null,\"boolStringVar\":\"+\"}";

    mockMvc.perform(post("/validateBoolean").contentType("application/json")
                    .content(postBody))
            .andExpect(status().isBadRequest());
 }

6.4. Validate Custom JSON Deserializer for Boolean Type

6.4.验证 Boolean 类型的自定义 JSON 反序列化器

Let’s validate the parameter marked with our custom JSON deserializer. The custom deserializer only accepts the values “+” and “-“. If we pass any other value, the validation will fail and trigger an error. Let’s pass the “plus” text value in the input JSON and see how the validation works:

让我们用自定义 JSON 反序列化器来验证标记的参数。自定义解串器只接受 “+”和”-“值。如果我们传递任何其他值,验证将失败并触发错误。让我们在输入的 JSON 中传递 “+”文本值,看看验证是如何工作的:

@Test
void whenInvalidBooleanFromJson_thenErrorResponse() throws Exception {
    String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"plus\"}";

    String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
            .content(postBody))
        .andReturn()
        .getResponse()
        .getContentAsString();

    assertEquals("Only values accepted as Boolean are + and -", output);
}

Finally, let’s test the happy case scenario. We’ll pass the “+” sign as input for the custom deserialized field. Since it’s a valid input, the validation will pass and give a successful response:

最后,让我们测试一下最理想的情况。我们将 “+”号作为自定义反序列化字段的输入。由于这是一个有效的输入,验证将通过并给出成功的响应:

 @Test
 void whenAllBooleanFieldsValid_thenCorrectResponse() throws Exception {
     String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}";

     String output = mockMvc.perform(post("/validateBoolean").contentType("application/json")
             .content(postBody))
         .andReturn()
         .getResponse()
         .getContentAsString();

     assertEquals("BooleanObject is valid", output);
 }

7. Validation in the Service

7.服务验证

Let’s now look at the validation at the service layer. To achieve this, we annotate the service class with @Validated annotation and place the @Valid annotation against the method parameter. The combination of these two annotations will cause Spring Boot to validate the object.

现在让我们来看看服务层的验证。为此,我们使用 @Validated 注解注解服务类,并将 @Valid 注解放在方法参数上。这两个注解的组合将导致 Spring Boot 验证对象。

Unlike @RequestBody at the controller layer, the validation at the service layer takes place against a simple Java object, hence the framework triggers a ConstraintViolationException for validation failure. Spring framework will return HttpStatus.INTERNAL_SERVER_ERROR for the response status in such cases.

与控制器层的 @RequestBody 不同,服务层的验证是针对简单的 Java 对象进行的,因此 框架会在验证失败时触发 ConstraintViolationException 异常。在这种情况下,Spring 框架将返回 HttpStatus.INTERNAL_SERVER_ERROR 作为响应状态。

Service level validations are preferred for objects created or amended at the controller layer and then passed to the service layer for processing.

对于在控制器层创建或修改,然后传递到服务层进行处理的对象,首选服务层验证。

Let’s check the skeleton of this service class:

让我们来看看这个服务类的骨架:

@Service
@Validated
public class ValidationService {

    public void processBoolean(@Valid BooleanObject booleanObj) {
        // further processing
    }
}

In the previous section, we created an endpoint to test the service layer and exception handler method to tackle ConstraintViolationException. Let’s write up a new test case to check this:

在上一节中,我们创建了一个端点来测试服务层和异常处理程序方法,以处理 ConstraintViolationException 异常。让我们编写一个新的测试用例来检查这一点:

@Test
void givenAllBooleanFieldsValid_whenServiceValidationFails_thenErrorResponse() throws Exception {
    mockMvc.perform(post("/validateBooleanAtService").contentType("application/json"))
        .andExpect(status().isInternalServerError());
}

8. Conclusion

8.结论

We learned how to validate the Boolean type at the controller and service layers with three approaches: programmatic validation, bean validation, and using a custom JSON deserializer. The reference code for this article is available over on GitHub.

我们了解了如何通过三种方法在控制器层和服务层验证 Boolean 类型:编程验证、Bean 验证和使用自定义 JSON 反序列化器。本文的参考代码可在 GitHub 上获取