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>2.1.214</version> 
    <scope>runtime</scope>
</dependency>

如上所示,我们引入了 spring-boot-starter-web 用于创建 REST 控制器。此外,建议检查 Maven Central 上 spring-boot-starter-jpaH2 数据库 的最新版本。

从 Spring Boot 2.3 开始,还需要显式添加 spring-boot-starter-validation 依赖:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</artifactId> 
</dependency>

3. 简单的领域类定义

依赖已经就位后,下一步是定义一个 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;
    
    // standard constructors / setters / getters / toString
        
}

这个 User 类非常简单,但它清晰地展示了如何利用 Bean Validation 的注解来约束字段。例如:

  • @NotBlank 表示该字段不能为空且去除前后空格后长度必须大于 0。
  • 使用 message 属性可以指定错误提示信息。

当然,Bean Validation 还提供了很多其他有用的约束注解,比如 @NotNull@Size@Email 等,可以根据需要组合使用。

为了将用户保存到 H2 数据库,我们还需定义一个简单的 Repository 接口:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

✅ 这样我们就拥有了基本的 CRUD 功能。

4. 实现 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
    
}

这段代码的核心在于使用了 @Valid 注解。

⚠️ 当 Spring Boot 发现方法参数上有 @Valid 注解时,会自动启用默认的 JSR 380 实现(也就是 Hibernate Validator)来进行校验。

如果校验失败,Spring Boot 会抛出 MethodArgumentNotValidException 异常。

5. 处理校验异常:@ExceptionHandler 注解

虽然 Spring Boot 能帮我们自动完成校验,但我们还需要处理校验失败的情况。

这时候就可以使用 @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;
}

📌 这个方法的作用是:

  • 将每个校验失败字段的名称和错误信息封装进一个 Map;
  • 最终以 JSON 形式返回给客户端。

这种方式简单粗暴又实用,尤其适合前后端分离的应用场景。

6. 测试 REST 控制器

我们可以使用集成测试来验证控制器的行为:

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

    @MockBean
    private UserRepository userRepository;
    
    @Autowired
    UserController userController;

    @Autowired
    private MockMvc mockMvc;

    //...
    
}

接着编写两个测试用例分别测试有效和无效的用户对象:

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
    MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
    String user = "{\"name\": \"bob\", \"email\" : \"[email protected]\"}";
    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\" : \"[email protected]\"}";
    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));
    }
}

✅ 可以看到,使用 MockMvc 非常方便地模拟 HTTP 请求并断言响应结果。

你也可以使用 Postman 等工具手动调用接口进行验证。

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", "[email protected]");
            User user2 = new User("Jenny", "[email protected]");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

启动后控制台会打印出插入的两个 User 对象。

发起一个有效的 POST 请求:

POST http://localhost:8080/users
{
  "name": "Alice",
  "email": "[email protected]"
}

会返回:

User is valid

而如果提交空字段:

{
  "name": "",
  "email": ""
}

则会返回:

{
  "name":"Name is mandatory",
  "email":"Email is mandatory"
}

8. 总结

在这篇文章中,我们学习了如何在 Spring Boot 中进行参数校验的基本操作。

总的来说,流程就是:

✅ 定义实体类并加上合适的校验注解
✅ 在 Controller 中使用 @Valid 触发校验
✅ 通过 @ExceptionHandler 统一处理校验失败情况

一如既往,本文所有示例代码都可以在 GitHub 找到。


原始标题:Validation in Spring Boot | Baeldung