如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里! (3)

某些场景下可能会需要我们手动校验并获得校验结果。

@Test public void check_person_manually() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Person person = new Person(); person.setSex("Man22"); person.setClassId("82938390"); person.setEmail("SnailClimb"); Set<ConstraintViolation<Person>> violations = validator.validate(person); //output: //email 格式不正确 //name 不能为空 //sex 值不在可选范围 for (ConstraintViolation<Person> constraintViolation : violations) { System.out.println(constraintViolation.getMessage()); } }

上面我们是通过 Validator 工厂类获得的 Validator 示例,当然你也可以通过 @Autowired 直接注入的方式。但是在非 Spring Component 类中使用这种方式的话,只能通过工厂类来获得 Validator。

@Autowired Validator validate 自定以 Validator(实用)

如果自带的校验注解无法满足你的需求的话,你还可以自定义实现注解。

案例一:校验特定字段的值是否在可选范围

比如我们现在多了这样一个需求:Person类多了一个 region 字段,region 字段只能是China、China-Taiwan、China-HongKong这三个中的一个。

第一步你需要创建一个注解:

@Target({FIELD}) @Retention(RUNTIME) @Constraint(validatedBy = RegionValidator.class) @Documented public @interface Region { String message() default "Region 值不在可选范围内"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

第二步你需要实现 ConstraintValidator接口,并重写isValid 方法:

import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; public class RegionValidator implements ConstraintValidator<Region, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { HashSet<Object> regions = new HashSet<>(); regions.add("China"); regions.add("China-Taiwan"); regions.add("China-HongKong"); return regions.contains(value); } }

现在你就可以使用这个注解:

@Region private String region; 案例二:校验电话号码

校验我们的电话号码是否合法,这个可以通过正则表达式来做,相关的正则表达式都可以在网上搜到,你甚至可以搜索到针对特定运营商电话号码段的正则表达式。

PhoneNumber.java

import javax.validation.Constraint; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Constraint(validatedBy = PhoneNumberValidator.class) @Target({FIELD, PARAMETER}) @Retention(RUNTIME) public @interface PhoneNumber { String message() default "Invalid phone number"; Class[] groups() default {}; Class[] payload() default {}; }

PhoneNumberValidator.java

import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> { @Override public boolean isValid(String phoneField, ConstraintValidatorContext context) { if (phoneField == null) { // can be null return true; } return phoneField.matches("^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|70)\\d{8}$") && phoneField.length() > 8 && phoneField.length() < 14; } }

搞定,我们现在就可以使用这个注解了。

@PhoneNumber(message = "phoneNumber 格式不正确") @NotNull(message = "phoneNumber 不能为空") private String phoneNumber; 使用验证组

某些场景下我们需要使用到验证组,这样说可能不太清楚,说简单点就是对对象操作的不同方法有不同的验证规则,示例如下(这个就我目前经历的项目来说使用的比较少,因为本身这个在代码层面理解起来是比较麻烦的,然后写起来也比较麻烦)。

先创建两个接口:

public interface AddPersonGroup { } public interface DeletePersonGroup { }

我们可以这样去使用验证组

@NotNull(groups = DeletePersonGroup.class) @Null(groups = AddPersonGroup.class) private String group; @Service @Validated public class PersonService { public void validatePerson(@Valid Person person) { // do something } @Validated(AddPersonGroup.class) public void validatePersonGroupForAdd(@Valid Person person) { // do something } @Validated(DeletePersonGroup.class) public void validatePersonGroupForDelete(@Valid Person person) { // do something } }

通过测试验证:

@Test(expected = ConstraintViolationException.class) public void should_check_person_with_groups() { Person person = new Person(); person.setSex("Man22"); person.setClassId("82938390"); person.setEmail("SnailClimb"); person.setGroup("group1"); service.validatePersonGroupForAdd(person); } @Test(expected = ConstraintViolationException.class) public void should_check_person_with_groups2() { Person person = new Person(); person.setSex("Man22"); person.setClassId("82938390"); person.setEmail("SnailClimb"); service.validatePersonGroupForDelete(person); }

使用验证组这种方式的时候一定要小心,这是一种反模式,还会造成代码逻辑性变差。

代码地址:https://github.com/Snailclimb/springboot-guide/tree/master/source-code/advanced/bean-validation-demo

@NotNull vs @Column(nullable = false)(重要)

在使用 JPA 操作数据的时候会经常碰到 @Column(nullable = false) 这种类型的约束,那么它和 @NotNull 有何区别呢?搞清楚这个还是很重要的!

@NotNull是 JSR 303 Bean验证批注,它与数据库约束本身无关。

@Column(nullable = false) : 是JPA声明列为非空的方法。

总结来说就是即前者用于验证,而后者则用于指示数据库创建表的时候对表的约束。

TODO

[ ] 原理分析

参考

https://reflectoring.io/bean-validation-with-spring-boot/

https://www.cnkirito.moe/spring-validation//

开源项目推荐

作者的其他开源项目推荐:

JavaGuide:【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。

springboot-guide : 适合新手入门以及有经验的开发人员查阅的 Spring Boot 教程(业余时间维护中,欢迎一起维护)。

programmer-advancement : 我觉得技术人员应该有的一些好习惯!

spring-security-jwt-guide :从零入门 !Spring Security With JWT(含权限验证)后端部分代码。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwypxy.html