在 Spring Boot 中校验 Boolean 类型
1、简介
在本文中,我们将学习如何在 Spring Boot 中的不同层(如 controller 或 service)上验证布尔(Boolean
)类型以及执行验证的各种方式。
2、编程式验证
Boolean
类提供了两个创建该类实例的基本方法:Boolean.valueOf()
和 Boolean.parseBoolean()
。
Boolean.valueOf()
可接受 String
和 boolean
值。它会检查输入参数的值是 true
还是 false
,并相应地提供一个 Boolean
对象。Boolean.parseBoolean()
方法只接受 String
值。
这些方法不区分大小写,例如,true
、True
、TRUE
、false
和 FALSE
都是可以的。
通过单元测试来验证 String
到 Boolean
的转换:
@Test
void givenInputAsString_whenStringToBoolean_thenValidBooleanConversion() {
assertEquals(Boolean.TRUE, Boolean.valueOf("TRUE"));
assertEquals(Boolean.FALSE, Boolean.valueOf("false"));
assertEquals(Boolean.TRUE, Boolean.parseBoolean("True"));
}
验证从基本 boolean
值到 Boolean
封装类的转换:
@Test
void givenInputAsboolean_whenbooleanToBoolean_thenValidBooleanConversion() {
assertEquals(Boolean.TRUE, Boolean.valueOf(true));
assertEquals(Boolean.FALSE, Boolean.valueOf(false));
}
3、使用自定义 Jackson Deserializer 进行验证
Spring Boot API 经常需要处理 JSON 数据,我们还要了解如何通过数据反序列化验证 JSON 到 Boolean
值的转换。我们可以使用自定义 deserializer 反序列化 boolean 值的自定义表示。
考虑这样一种情况,即我们要解析表示 boolean
值的 JSON 数据,该布尔值包含 +
(表示 true
)和 -
(表示 false
)。
编写一个 JSON deserializer 来实现:
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)
Bean Validation 约束是验证字段的另一种常用方法。要使用这种方法,需要添加 Spring-boot-starter-validation
依赖。在所有可用的验证注解中,有三个可用于 Boolean
字段:
@NotNull
: 如果Boolean
字段为空,则验证失败。@AssertTrue
: 如果Boolean
字段设置为false
,则验证失败。@AssertFalse
: 如果Boolean
字段设置为true
,则验证失败。
注意,@AssertTrue
和 @AssertFalse
都将 null
值视为合法值。也就是说,如果我们想确保只接受实际的 boolean 值,就需要将这两个注解与 @NotNull
结合使用。
5、Boolean Validation 示例
在 Controller 层和 Service 层使用 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;
// get / set 方法省略
}
6、Controller 中的校验
当通过 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");
}
}
校验完成后,如果发现任何校验失败的情况,Spring 会抛出 MethodArgumentNotValidException
。可以使用带有相关 ExceptionHandler
方法的 ControllerAdvice
来处理校验失败异常。
创建三个方法,分别处理 controller 层和 service 层抛出的异常:
@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();
}
}
在测试 REST 功能之前,建议先在 Spring Boot 中测试 API。
创建 controller 测试类:
@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;
}
有了这些,我们现在就可以测试我们在类中使用的 Validation 注解了。
6.1、@NotNull
注解
让我们看看 @NotNull
是如何工作的。当我们传递带有 null
Boolean
参数的 BooleanObject
时,@Valid
注解将校验 bean 并抛出一个 “400 Bad Request” 的 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、@AssertTrue
注解
接下来,测试 @AssertTrue
。当我们传递带有 false
Boolean
值参数的 BooleanObject
时,@Valid
注解将校验 bean 并抛出一个 “400 Bad Request” 的 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);
}
如果提供 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、@AssertFalse
注解
现在我们来了解 @AssertFalse
的工作原理。当为 @AssertFalse
参数传递一个 true
值时,@Valid
注解会抛出一个错误的请求(Bad Request)。我们可以在响应体中获取针对 @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);
}
我们再来看看如果提供 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、自定义 JSON Deserializer 来验证 Boolean
类型
使用自定义 JSON deserializer 来验证标记的参数。自定义 deserializer 只接受 +
和 -
值。如果传递任何其他值,验证将失败并抛出异常。
在输入的 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);
}
最后,测试一下最理想的情况。将 +
号作为自定义反序列化字段的输入。由于这是一个有效的输入,验证将通过并给出成功的响应。
@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、Service 中的校验
现在我们来看看 service 层的校验。使用 @Validated
注解 service 类,并将 @Valid
注解放在方法参数上。这两个注解的组合会对方法参数进行校验。
与 controller 层的 @RequestBody
不同,service 层的校验是针对简单的 Java 对象进行的,因此框架会在校验失败时抛出 ConstraintViolationException
异常。在这种情况下,Spring 框架会返回 HttpStatus.INTERNAL_SERVER_ERROR
状态的响应。
在 controller 层创建或修改对象,然后将其传递到 service 层进行处理时,最好使用 service 层验证。
service 类大体如下:
@Service
@Validated
public class ValidationService {
public void processBoolean(@Valid BooleanObject booleanObj) {
// 业务逻辑
}
}
在前面的部分中,我们创建了一个端点来测试 service 层,并编写了异常处理方法来处理 ConstraintViolationException
。
现在编写一个新的测试用例来检查这个功能:
@Test
void givenAllBooleanFieldsValid_whenServiceValidationFails_thenErrorResponse() throws Exception {
mockMvc.perform(post("/validateBooleanAtService").contentType("application/json"))
.andExpect(status().isInternalServerError());
}
8、总结
本文详细介绍了如何通过编程式验证、Bean Validation 注解和使用自定义 JSON deserializer 在 controller 层和 service 层验证 Boolean 类型。
参考:https://www.baeldung.com/spring-boot-validate-boolean-type