Spring Boot 中的数据校验
1、概览
Spring Boot 通过 Hibernate Validator(Bean Validation 的实现)对数据验证提供了强大的支持。
本文将通过一个实际的 REST 应用带你了解如何在 Spring Boot 中校验数据。
2、Maven 依赖
在 pomx.ml
中添加 spring-boot-starter-web
、spring-boot-starter-jpa
和 H2
database 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<scope>runtime</scope>
</dependency>
从 Boot 2.3 开始,还需要明确添加 spring-boot-starter-validation
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3、示例 Domain 类
定义一个 JPA 实体类,User
:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotBlank(message = "Name is mandatory")
private String name;
@NotBlank(message = "Email is mandatory")
private String email;
// 构造函数、Set、Set 方发省略
}
User 实体类很简单,它展示了如何使用 Bean Validation 的约束来限制 name
和 email
字段。
为简单起见,这里仅使用 @NotBlank
约束来约束目标字段。此外,还使用 message
属性指定了错误信息。
因此,当 Spring Boot 验证类实例时,受约束字段必须不为 null
,且去除其两边空白后长度必须大于零。
除了 @NotBlank
之外,Bean Validation 还提供了许多其他方便的约束。可以对受约束的类应用和组合不同的验证规则。如需了解更多信息,请阅读官方的 Bean Validation 文档。
还要定义一个 UserRepository
,用于把 User
保存到 H2 数据库:
@Repository
public interface UserRepository extends CrudRepository<User, Long> {}
4、REST Controller
创建 UserController
:
@RestController
public class UserController {
@PostMapping("/users")
ResponseEntity<String> addUser(@Valid @RequestBody User user) {
// 存 储User
return ResponseEntity.ok("User is valid");
}
当 Spring Boot 发现一个参数注解了 @Valid
时,它会自动启动默认的 JSR 380 实现 - Hibernate Validator,并验证该参数。
当目标参数未能通过验证时,Spring Boot 会抛出一个 MethodArgumentNotValidException
异常。
5、@ExceptionHandler 注解
Spring Boot 能自动验证传递给 addUser()
方法的 User
对象,这确实很方便,但这一过程中缺少的是如何处理验证结果?
@ExceptionHandler
注解允许我们通过一个方法处理指定类型的异常。
因此,可以用它来处理验证错误:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
如上,指定了 MethodArgumentNotValidException
异常作为要处理的异常。因此,当指定的 User
对象无效时,Spring Boot 将调用此方法。
该方法将每个无效字段的名称和验证后错误信息存储在 Map
中。然后,将 Map
以 JSON 表示形式发送回客户端,以便进一步处理。
6、测试 REST Controller
创建测试类,并注入 Mock UserRepository
接口实现、UserController
实例和 MockMvc
对象:
@RunWith(SpringRunner.class)
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
@MockBean
private UserRepository userRepository;
@Autowired
UserController userController;
@Autowired
private MockMvc mockMvc;
//...
}
由于只测试 Web 层,因此使用 @WebMvcTest
注解。它允许我们使用 MockMvcRequestBuilders
和 MockMvcResultMatchers
实现的静态方法集轻松测试请求和响应。
现在,在请求体中传递有效和无效的 User
对象来测试 addUser()
方法:
@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content()
.contentType(textPlainUtf8));
}
@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
.andExpect(MockMvcResultMatchers.content()
.contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
也可以使用免费的 API 测试工具(如 Postman)来测试 REST Controller API。
7、运行示例应用
使用标准的 main()
方法运行示例项目:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner run(UserRepository userRepository) throws Exception {
return (String[] args) -> {
User user1 = new User("Bob", "bob@domain.com");
User user2 = new User("Jenny", "jenny@domain.com");
userRepository.save(user1);
userRepository.save(user2);
userRepository.findAll().forEach(System.out::println);
};
}
}
你可以在控制台中看到打印出来的几个 User
对象。
使用有效的 User
对象向 http://localhost:8080/users
端点发出 POST 请求,将返回字符串 “User is valid”。
使用不含 name
和 email
值的 User
对象进行 POST 请求,会返回以下响应:
{
"name":"Name is mandatory",
"email":"Email is mandatory"
}
8、总结
本文介绍了如何在 Spring Boot 中使用 @Valid
注解校验客户端提交的数据,以及如何处理校验失败的异常。
Ref:https://www.baeldung.com/spring-boot-bean-validation