文章目录
如果你有前端传参校验的需求,而又不想手动写if去校验的话,本文应该可以帮到你 :)
Spring Validation概述
Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。
1、基本使用
如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需手动导入依赖
版本说明:springboot 2.6.1
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
分两类参数传递进行测试
一般来说
1、POST、PUT请求,使用@RequestBody传递参数;
2、GET请求,使用@RequestParam / @PathVariable传递参数。
1.1、首先以 新增用户信息 为示例来测试第一类
新建dto
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description: 下面的注解,见名知意
*/
@Data
public class User {
@NotNull
@Length(min = 2, max = 10)
private String username;
@NotNull
@Length(min = 2, max = 10)
private String pwd;
private String id;
}
新建接口测试
import com.example.springvalidation.dto.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@RestController
public class TestController {
/**
* 使用 @RequestBody @Validated 注解实现参数校验
* @param user
* @return
*/
@PostMapping("save")
public String saveUser(@RequestBody @Validated User user){
return "validation success";
}
}
1.1.1、正常测试
1.1.2、长度测试
或短或长皆会由spring转为400
同时控制台,会打印消息 “长度需要在2和10之间”
2021-12-17 13:26:13.950 WARN 19524 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.springvalidation.controller.TestController.saveUser(com.example.springvalidation.dto.User): [Field error in object 'user' on field 'username': rejected value [1]; codes [Length.user.username,Length.username,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username],10,2]; default message [长度需要在2和10之间]] ]
1.1.3、@NotNull 测试
由结果看, null
会进行拦截,美中不足的是 ""
会以长度不足的分支去拦截
自定义消息提示
@NotNull(message="null自定义提示")
@Length(min = 2, max = 10, message="长度自定义提示")
1.2、第二类以 获取用户信息 @RequestParam / @PathVariable 测试
新增接口,注意类上面要加@Validated注解
import com.example.springvalidation.dto.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Min;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@RestController
@Validated
public class TestController {
/**
* 使用 @RequestBody @Validated 注解实现参数校验
*
* @param user
* @return
*/
@PostMapping("save")
public String saveUser(@RequestBody @Validated User user) {
return "validation success";
}
@GetMapping("{id}")
public String getUser(@PathVariable("id") @Min(0L) int id) {
return "validation success";
}
/**
* 测试发现,@RequestParam("id")有没有对于测试结果没有影响
*/
@GetMapping("get")
public String getUser2(@RequestParam("id") @Min(0L) @NotNull int id) {
return "validation success 2";
}
}
当出现不符合要求的id时
getUser2 接口测试,当分别访问
localhost:8080/get?id=null
localhost:8080/get?id=""
localhost:8080/get?id=-1
结果分别为
2、进阶使用
2.1、分组校验
dto修改如下
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@Data
public class User {
@NotNull(message="null自定义提示")
@Length(min = 2, max = 10, message="长度自定义提示")
private String username;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String pwd;
private String id;
public interface Save { }
public interface Update { }
}
接口修改如下:
@PostMapping("save")
public String saveUser(@RequestBody @Validated(User.Save.class) User user) {
return "validation success";
}
自测~
2.2、嵌套校验
当一个对象携带其他对象内的参数也需要校验的时候,可以使用 @Valid 来进行嵌套,可以重新定义Authority 类的校验规则,也可重新建一个类,进行校验规则复用
dto修改如下
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@Data
public class User {
@NotNull(message="null自定义提示")
@Length(min = 2, max = 10, message="长度自定义提示")
private String username;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String pwd;
private String id;
@NotNull(groups = {Save.class, Update.class})
@Valid
private Authority authority;
@Data
public static class Authority {
@Min(value = 1, groups = Update.class)
private Long id;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String name;
//更多...
}
public interface Save { }
public interface Update { }
}
这样 对于 User 内的所有字段都会校验,包括 Authority 内部定义的规则
细心的会发现了一个问题
@Valid和@Validated区别
区别 | @Valid | @Validated |
---|---|---|
提供者 | JSR-303 规范 | Spring |
是否支持分组 | 不支持 | 支持 |
标注位置 | METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE | TYPE, METHOD, PARAMETER |
嵌套校验 | 支持 | 不支持 |
2.3 、集合测试
当前端直接传递json字符串来时,可自定义集合测试
新增dto,ValidationList
import lombok.experimental.Delegate;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
public class ValidationList<E> implements List<E> {
/**
* @Delegate注解 lombok版本1.18.6以上方可支持
*/
@Delegate
@Valid
public List<E> list = new ArrayList<>();
@Override
public String toString() {
return list.toString();
}
}
新增接口
@PostMapping("/saveList")
public String saveList(@RequestBody @Validated(User.Save.class) ValidationList<User> userList) {
return "validation success";
}
这样整个json里面的参数,就会按照我们定义的规则(User)进行校验,按照规则,各位看官可根据规则,键入不合符规则数据自行测试
2.4 、自定义校验
实现 ConstraintValidator 接口,编写约束校验器,即定义校验规则,这里我们验证密码,由数字或者a-d的字母组成,2-10长度
import com.example.springvalidation.annotation.SjtValidation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
public class SjtValidator implements ConstraintValidator<SjtValidation, String> {
//自定义校验规则
private static final Pattern PATTERN = Pattern.compile("^[a-d\\d]{2,10}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value != null) {
Matcher matcher = PATTERN.matcher(value);
return matcher.find();
}
return true;
}
}
新增注解
import com.example.springvalidation.validator.SjtValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {SjtValidator.class})
public @interface SjtValidation {
//模仿现有的注解,实现默认的提示消息返回
String message() default "密码格式错误";
//支持分组
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
为了干净,新增dto,User2,使用 @SjtValidation
import com.example.springvalidation.annotation.SjtValidation;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author Song Jiangtao
* @date 2021/12/17
* @description:
*/
@Data
public class User2 {
private String id;
private String username;
@NotNull(groups = {User.Save.class, User.Update.class})
@SjtValidation
private String pwd;
}
新增接口
@PostMapping("save2")
public String saveUser2(@RequestBody @Validated User2 user2) {
return "validation success";
}
测试即可,可以发现,我们的密码需要按照我们的规则来进行传递,各位看官可在此基础上自由发挥,自由验证,自由组合前面的各种校验测试
2.5 编程式校验
注入javax.validation.Validator
,调用api进行校验
@Resource
private javax.validation.Validator validator;
@PostMapping("/save3")
public String saveUser3(@RequestBody User user) {
Set<ConstraintViolation<User>> validate = validator.validate(user, User.Save.class);
if (validate.isEmpty()) {
return "validation failed";
} else {
for (ConstraintViolation<User> constraintViolation : validate) {
System.out.println(constraintViolation);
}
return "validation success";
}
}
这里不在进行测试,我个人不是特别喜欢这种方式。。
3、fail-fast
Spring Validation默认会校验完所有字段,然后才抛出异常,启动此机制,碰到错误的校验直接返回,后面的字段不在校验
启动类添加bean实现快速失败
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@SpringBootApplication
public class SpringvalidationApplication {
//校验快速失败
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
public static void main(String[] args) {
SpringApplication.run(SpringvalidationApplication.class, args);
}
}
4、更多注解
5、对集合的校验支持
自定义ValidList 实现 List
package com.trinasolar.csde.performance.api.validate;
import lombok.Data;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/**
* @author Song Jiangtao
* @date 2022/2/23
* @description:
*/
@Data
public class ValidList<E> implements List<E> {
@Valid
private List<E> list = new ArrayList<>();
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
return list.add(e);
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return list.addAll(index,c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public void clear() {
list.clear();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
return list.set(index,element);
}
@Override
public void add(int index, E element) {
list.add(index,element);
}
@Override
public E remove(int index) {
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
@Override
public ListIterator<E> listIterator() {
return list.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return list.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex,toIndex);
}
}
使用ValidList 代替 List
更多的内容需要各位看官自行去挖掘啦~