1. 概述
在验证用户输入方面,Spring Boot为这种常见但关键的任务提供了强大的支持,直接开箱即用。
尽管Spring Boot支持与自定义验证器的无缝集成,但执行验证的事实标准是Hibernate Validator,即Bean Validation框架的参考实现。
在本教程中,我们将了解如何在Spring Boot中验证域对象。
2. Maven依赖
在本例中,我们将学习如何通过构建一个基本的REST控制器来验证Spring Boot中的域对象。
控制器将首先获取一个域对象,然后使用Hibernate Validator对其进行验证,最后将其持久化到内存中的H2数据库中。
该项目的依赖项是相当标准的:
<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>1.4.197</version>
<scope>runtime</scope>
</dependency>
如上所示,我们在pom.xml文件中包含了spring-boot-starter-web,因为我们需要它来创建REST控制器。此外,让我们确保检查最新版本的spring-boot-starter-data-jpa和Maven Central上的H2数据库。
从Boot 2.3开始,我们还需要显式添加spring-boot-starter-validation依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3. 一个简单的域类
有了我们项目的依赖项,接下来我们需要定义一个示例JPA实体类,其角色将仅用于对用户进行建模。
让我们来看看这个类:
@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;
// standard constructors / setters / getters / toString
}
我们的User实体类的实现确实很贫乏,但它简要地说明了如何使用Bean Validation的约束注解来约束name和email字段。
为了简单起见,我们仅使用@NotBlank约束来约束目标字段。此外,我们还使用注解的message属性指定了错误消息。
因此,当Spring Boot验证类实例时,约束字段必须不为null,并且它们的修剪长度必须大于0。
此外,除了@NotBlank之外,Bean Validation还提供了许多其他方便的约束注解,这允许我们将不同的验证规则应用于约束类并组合在一起。有关详细信息,请阅读官方Bean Validation文档。
由于我们将使用Spring Data JPA将用户保存到内存中的H2数据库中,因此我们还需要定义一个简单的Repository接口,以便在User对象上具有基本的CRUD功能:
@Repository
public interface UserRepository extends CrudRepository<User, Long> {}
4. 实现REST控制器
当然,我们需要实现一个Web层,允许我们能够获取分配给User对象的约束字段的值。因此,我们可以验证它们并根据验证结果执行一些进一步的任务。
Spring Boot通过REST控制器的实现使这个看似复杂的过程变得非常简单。
让我们看看REST控制器的实现:
@RestController
public class UserController {
@PostMapping("/users")
ResponseEntity<String> addUser(@Valid @RequestBody User user) {
// persisting the user
return ResponseEntity.ok("User is valid");
}
// standard constructors / other methods
}
在Spring REST上下文中,addUser()方法的实现是相当标准的。当然,最相关的部分是@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的表示形式发送回客户端以供进一步处理。
简而言之,REST控制器允许我们能够轻松地处理对不同端点的请求、验证用户对象并以JSON格式发送响应。
该设计足够灵活,可以通过多个Web层处理控制器响应,范围从模板引擎(例如Thymeleaf)到功能齐全的JavaScript框架(例如Angular)。
6. 测试REST控制器
我们可以通过集成测试轻松测试REST控制器的功能。
让我们开始Mock/自动装配UserRepository接口实现,以及UserController实例和MockMvc对象:
@ExtendWith(SpringExtension.class)
@WebMvcTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@MockBean
private UserRepository userRepository;
@Autowired
UserController userController;
@Autowired
private MockMvc mockMvc;
// ...
}
由于我们只测试Web层,因此我们使用@WebMvcTest注解,它允许我们使用由MockMvcRequestBuilders和MockMvcResultMatchers类实现的一组静态方法轻松测试请求和响应。
现在让我们使用在请求正文中传递的有效和无效User对象来测试addUser()方法:
@Test
void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8);
String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(textPlainUtf8));
}
@Test
void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON));
}
此外,我们可以使用免费的API生命周期测试应用程序(例如Postman)来测试REST控制器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”。
同样,传递不带名称和电子邮件值的User对象的POST请求将返回以下响应:
{
"name": "Name is mandatory",
"email": "Email is mandatory"
}
8. 总结
在本文中,我们了解了在Spring Boot中执行验证的基础知识。
与往常一样,本教程的完整源代码可在GitHub上获得。