Hibernate Validator的英文官方文档:https://hibernate.org/validator/documentation/
Hibernate Validator的作用
Hibernate Validator是一个校验框架。我们写项目时经常会校验邮箱,密码之类的数据格式,传统的方法是在service层写校验逻辑,非常繁琐。而且代码重用性低。而使用Hibernate Validator框架,只需要在想要校验的属性上加上注解,当校验失败时,框架会自动处理。
使用
引入依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
写一个实体类,在属性上添加注解
@NotBlank(message = "密码不能为空")
private String password;
@Past(message = "日期必须为过去的日期")
private Date birthday;
注解的作用我已经在message属性中说明了,不写这个message他也会有一个默认的信息。
message属性是在参数出错时返回的错误消息。
编写一个控制器
@PostMapping
@JsonView(User.UserSimpleView.class)
public User createUser(@Valid @RequestBody User user, BindingResult errors){
// 打印错误消息
if (errors.hasErrors())
errors.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
// 利用反射打印用户信息。commons.lang中
System.out.println(ReflectionToStringBuilder.toString(user,
ToStringStyle.MULTI_LINE_STYLE));
user.setId(1);
return user;
}
@Valid的作用是对这个参数执行校验,不写的话上面的注解不会起作用
BindingResult 的作用是当发生错误时,使可以携带错误信息进入到方法内。我在if语句中讲错误信息打印了一下。不写的话当验证失败时该请求会被直接打回去,报400。
编写测试用例
@Test
public void whenCreateSuccess() throws Exception {
// 创建一个日期,时间戳在一年之后。LocalDateTime是jdk8中的类
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
System.out.println(date);
// 请求体
String content = "{\"username\":\"tom\",\"birthday\":\"" + date.getTime() + "\",\"password\":null}";
String contentAsString = mockMvc.perform(post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(contentAsString);
}
我们的密码故意为空,时间设置为未来的日期,看执行情况。
测试成功了,并且把我们在注解上写的message打印了出来
其他常用注解
自定义注解
有些情况下,我们需要判断参数是否合法的逻辑不是这么简单。比如用户输入了用户名,我们需要判断用户名是否重复,这需要去数据库查询的,显然不可能预定好。这时候,就需要我们来自定义注解。
1.新建一个注解
// 注解可以作用的位置:字段、方法
@Target({ElementType.FIELD, ElementType.METHOD})
// 运行时注解
@Retention(RetentionPolicy.RUNTIME)
// 制定注解判断逻辑所在的类,这个类必须实现了ConstraintValidator接口
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
注解的这三个属性必须有,这三个属性你可以在任意一个比如 @NotBlank注解中看到。message属性就是失败时返回的信息,其他两个作者暂时也不清楚。
2.新建一个类实现ConstraintValidator接口
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
@Autowired
private TestService testService;
/**
* 初始化的方法
* @param myConstraint 我们自定义的注解
*/
@Override
public void initialize(MyConstraint myConstraint) {
System.out.println("my validator is init...");
}
/**
* 判断的具体逻辑
* @param o 注解所作用的对象
* @param constraintValidatorContext 上下文
* @return
*/
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
testService.getUserName((String) o);
return false;
}
}
这个类可以随意注入bean且不需要为这个类声明@Component注解,spring看到他实现了ConstraintValidator接口会为他自动注入。所以可以方便的注入我们的service服务来判断校验逻辑。
使用这个注解
@MyConstraint(message = "用户名已存在")
private String username;
再执行一次之前的测试用例
可以看到,我们的业务逻辑成功执行了