Kehaw

Spring Boot中使用JSR380验证框架


Spring boot 是开箱即用的一个框架集,它将所有常用的框架集成在一起,同时为所有的使用者提供了 JAR 包版本号的检测,保证所有被集成进来的工具、框架没有冲突。因此,Spring Boot 理所当然的也集成了常见的 Bean Validation 框架。

实际上,Bean Validation框架经历了JSR303、JSR349现在已经迎来了JSR380的标准实现,他们分别由 Bean Validation的1.01.12.0来实现,利用好这个验证框架,能够有效的减少开发中引发的手误和节省代码量,如果在2019年,你还对这个框架感到陌生的话,你就需要审视一下自己是否太久没有更新过自己的知识了。

尽管Spring Boot支持与自定义验证器的无缝集成,但事实上,执行验证的标准是 Hibernate Validator,它是Bean Validation框架的最佳实现。

在本教程中,我们将研究如何在Spring Boot中验证域对象。

POM依赖

在这个案例中,我们将学习如何通过@RestController来搭建基本的验证。

我们的案例将使用 Hibernate Validator 对其进行验证,然后将数据存入到H2内存数据库中。

依赖很简单,主要就是Spring WEB、JPA以及H2数据库。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</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>

使用Spring WEB的目的在于我们需要通过浏览器来方便的测试Validation是否成功,而lombok是为了简化我们的实体类,省略掉gettersetter等一些建模方法。

一个简单的Domain

有了我们项目的依赖关系之后,接下来,我们需要定义一个示例JPA实体类,其作用将只是为用户建模。

@Entity
@Data
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;
         
}

我们的实体类非常简单,主要目的就是为了简单的验证nameemail两个字段的合法性。

在这个实体类中,为了简单起见,我们仅仅使用@NotBlank来验证两个字段不能为空,而在实际的开发场景中,Bean Validation 框架提供了更多的注解用来验证字段合法性。

@NotBlank的作用主要是用来校验String类型的字段必须不为空,并且其trim()后的长度必须大于零。

在验证框架的注释上,我们为每一个验证加入了错误的验证信息message(当验证失败的时候,这些消息会被使用到)。

由于我们将使用Spring Data JPA将用户保存到内存中的H2数据库,因此我们还需要定义一个简单的存储库接口,以在User对象上具有基本的CRUD功能:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}
编写一个RestController

我们需要编写一个@RestController来实验我们的校验是否成功,当然,使用@Controller也是一样的,只是Rest Controller代码更简洁一些。

@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 Context将自动的引导JSR 380去调用Hibernate的验证实现去校验参数,当校验不通过的时候,会抛出MethodArgumentNotValidException异常。

统一异常处理

虽然让Spring Boot自动验证传递给addUser()方法的User对象确实很方便,但是我们无法控制验证结果的定制化错误返回。

@ExceptionHandler注释可以达到统一异常处理的目的,因此,我们可以使用它来处理验证错误:

@RestControllerAdvice
public class ExceptionHandlers {

    @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将调用此方法来返回错误信息。

通常这个统一异常处理我们会写在@RestControllerAdvice@ControllerAdvice注释的类中。

测试

我们可以通过集成测试轻松地测试REST Controller的功能。

模拟/自动装配UserRepository接口实现,以及UserController实例和MockMvc对象:

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
 
    @MockBean
    private UserRepository userRepository;
     
    @Autowired
    UserController userController;
 
    @Autowired
    private MockMvc mockMvc;
 
    //...
     
}

我们测试addUser()方法,并在请求正文中传递一个有效的和一个无效的User对象:

@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));
    }
}

当然,我们也可以通过Postman之类的测试工具来进行测试,这里就不细说了。

Kehaw

👨‍💻Ke Haw 🇨🇳👨‍👩‍👧‍👦

风吹云散去,夜色好观星
Java | 前端 | 大数据

专注于 Spring Cloud 微服务架构与数据处理,研究一切与Java相关的开发技术,包括一部分前端技术。

目前的工作主要是关于B2B大宗商品在线交易领域的数据处理。如果对本站的部分内容感兴趣,请通过邮件、Twitter联系我🤝。

Fork me on Gitee
基于Spring Security + OAuth2 + JWT 的权限认证(一) Java-Stream学习第四步:数据处理 Java-Stream学习第三步:终端操作 Java-Stream学习第二步:处理流 Java-Stream学习第一步:创建流 Electron使用串口通信 Electron下调用DLL文件 国外SaaS服务供应商都是干什么的:Part1 为什么Kafka会丢失消息 Spring Boot中使用JSR380验证框架
Description lists
Kehaw's blog
Site description
人初做事,如鸡伏卵,不舍而生气渐充;如燕营巢,不息而结构渐牢;如滋培之木,不见其长,有时而大;如有本之泉,不舍昼夜,盈科而后进,放乎四海。
Copyright
© 2014 Copyright Kehaw | All rights reserved.