SpringBoot入门建站全系列(三十三)集成validator校验接口数据
SpringBoot入门建站全系列(三十三)集成validator校验接口数据
一、概述
在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,如果我们直接将这些校验写死在代码里,将会遇到这种现象:
- 验证代码繁琐,重复劳动
- 方法内代码显得冗长
- 代码可读性不高
所以,我们可以使用hibernate validator来对字段的校验工作统一完成。
spring-boot-starter-web中默认引入了hibernate-validator,因此,在SpringBoot项目中,我们可以直接使用hibernate-validator的特性。
这一篇篇文章本应该放在SpringBoot入门建站全系列的前面章节讲述,这里权做对该系列的补充了。
**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a
href=" https://jq.qq.com/?_wv=1027&k=52sgH1J "
target="_blank">
加入我们的java学习圈,点击即可加入
</a>
,共同学习,节约学习时间,减少很多在学习中遇到的难题。**
二、基础知识
首先,基础知识是必须要了解的,我这里整理了下最新的注解,比网上一下子搜到的博客更全面。
validator的功能是由hibernate-validator提供的,所以在Spring官方文档里是找不到关于它的说明的,需要到hibernate-validator官网查看:
注解 |
释义 |
场景 |
---|---|---|
@AssertFalse |
验证注解的元素值是false |
Boolean, boolean |
@AssertTrue |
验证注解的元素值是true |
Boolean, boolean |
@DecimalMax(value=x) |
验证注解的元素值小于等于@ DecimalMax指定的value值 |
BigDecimal,BigInteger,CharSequence,byte,short,int,long和原始类型的相应的包装类; |
@DecimalMin(value=x) |
验证注解的元素值小于等于@ DecimalMin指定的value值 |
BigDecimal,BigInteger,CharSequence,byte,short,int,long和原始类型的相应的包装类; |
@Digits(integer=整数位数, fraction=小数位数) |
验证注解的元素值的整数位数和小数位数上限 |
BigDecimal的,BigInteger,CharSequence,byte,short,int,long和原始类型的相应的包装类; |
@Future |
检查带注释的日期是否是将来 |
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; |
@FutureOrPresent |
检查带注释的日期是现在还是将来 |
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; |
@Max(value=x) |
验证注解的元素值小于等于@Max指定的value值 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类; |
@Min(value=x) |
验证注解的元素值大于等于@Min指定的value值 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类; |
@NotNull |
验证注解的元素值不是null |
任意 |
@Null |
验证注解的元素值是null |
任意 |
@Past |
检查带注释的日期是否是过去 |
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; |
@PastOrPresent |
检查带注释的日期是过去还是现在 |
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate; |
@Pattern(regex=正则表达式, flag=) |
验证注解的元素值与指定的正则表达式匹配 |
CharSequence |
@Size(min=最小值, max=最大值) |
验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
CharSequence,Collection,Map和数组 |
@Valid |
验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象 |
Any non-primitive type(引用类型) |
@NotEmpty |
验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
CharSequence,Collection,Map和数组 |
@Range(min=最小值, max=最大值) |
验证注解的元素值在最小值和最大值之间 |
CharSequence, Collection, Map and Arrays,BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective wrappers of the primitive types |
@NotBlank |
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
CharSequence |
@Length(min=下限, max=上限) |
验证注解的元素值长度在min和max区间内 |
CharSequence |
|
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
CharSequence |
@Negative |
检查元素是否严格为负。零值被视为无效。 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类 |
@NegativeOrZero |
检查元素是负数还是零。 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类 |
@Positive |
检查元素是否严格为正。零值被视为无效。 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类 |
@NegativeOrZero |
检查元素是正数还是零。 |
BigDecimal,BigInteger,byte,short,int,long和原始类型的相应的包装类 |
@URL(protocol=, host=, port=, regexp=, flags=) |
检查字符序列是否为有效URL。 |
CharSequence |
@CreditCardNumber |
检查带注释的字符序列是否通过了Luhn校验和测试。 |
CharSequence |
@Currency |
检查带注释的货币单位javax.money.MonetaryAmount是否为指定货币单位的一部分。 |
CharSequence |
@DurationMax |
检查带注释的java.time.Duration元素不大于由注释参数构造的元素。 |
java.time.Duration |
@DurationMin |
检查带注释的java.time.Duration元素不少于由注释参数构造的元素。 |
java.time.Duration |
@EAN |
检查带注释的字符序列是有效的EAN条形码。 |
CharSequence |
@ISBN |
检查带注释的字符序列是有效的ISBN。 |
CharSequence |
@CodePointLength |
验证带注释的字符序列的代码点长度在之间min并max包括在内。 |
CharSequence |
@LuhnCheck |
检查带注释的字符序列中的数字是否通过Luhn校验和算法 |
CharSequence |
@Mod10Check |
检查带注释的字符序列中的数字是否通过通用mod 10校验和算法。 |
CharSequence |
@Mod11Check |
检查带注释的字符序列中的数字是否通过了mod 11校验和算法。 |
CharSequence |
@SafeHtml |
检查带注释的值是否包含潜在的恶意片段,例如<script/>。 |
CharSequence |
@ScriptAssert |
检查是否可以根据带注释的元素成功评估给定脚本。 |
任意 |
@UniqueElements |
检查带注释的集合仅包含唯一元素。 |
Collection |
三、实体校验
假设当前有个实体叫userInfo
3.1 实体
package com.cff.springbootwork.validator.vo;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Email;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Negative;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Positive;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserInfo {
@Null(message = "创建时间不能填")
@JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
private Date createTime;
@NotEmpty(message = "用户名不能为空")
private String userName;
@NotBlank(message = "姓名不能为空或空字符串")
private String name;
@Negative(message = "冬天温度在0°以下")
private Integer temperatureWinter;
@Positive(message = "夏天温度在0°以上")
private Integer temperatureSummer;
@Digits(integer = 11, message = "手机号是11位整数哦", fraction = 0)
private String mobile;
@NotNull(message = "年龄不能为空")
@Min(value = 10, message = "年龄太小了")
@Max(value = 35, message = "年龄太大了")
private Integer age;
@Size(min = 0, max = 2, message = "你女朋友个数在0-2之间")
private List<String> girlFrinds;
@Range(min = 0, max = 100, message = "你钱包里的钱在0-2之间")
private Integer money;
@Length(min = 4, max = 64, message = "地址在4-64之间")
private String address;
@AssertTrue(message = "对象必须是人")
private Boolean people;
@AssertFalse(message = "不能上来就删除")
private Boolean delete;
@Pattern(regexp="[0-9]{6}",message = "密码格式错误")
private String password;
@Email(message = "email格式错误")
private String email;
@JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
@Future(message = "失效时间比当前时间晚")
private Date expireTime;
@JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
@Past(message = "出生日期比当前时间早")
private Date birthDate;
@URL(message = "url填写错误")
private String url;
}
3.2 Web层数据接收
只需要加上@Valid注解即可,然后通过BindingResult来接收校验错误。
@RequestMapping(value = "/test")
public List<String> set(@Valid @RequestBody UserInfo userInfo, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errorMsg = bindingResult.getAllErrors().stream().map(s -> s.getDefaultMessage())
.collect(Collectors.toList());
return errorMsg;
return Collections.singletonList("0000");
}
这里,是打印了所有错误结果,如果只校验是否错误,抛出第一个错误,这样写即可:
@RequestMapping(value = "/test")
public List<String> set(@Valid @RequestBody UserInfo userInfo, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String errorMsg = bindingResult.getAllErrors().get(0).getDefaultMessage();
return Collections.singletonList(errorMsg);
return Collections.singletonList("0000");
}
3.3 校验不通过测试
请求参数:
"createTime":"2018-08-09",
"userName": "",
"name": " ",
"age": 9,
"mobile": "123123123",
"girlFrinds": ["1号","2号","3号"],
"money": 101,
"temperatureWinter": 0,
"temperatureSummer": -1,
"address": "12",
"people": false,
"delete": true,
"password": "123",
"email": "11@",
"expireTime":"2019-11-11",
"birthDate":"2020-11-11",
"url":"qwe"
返回结果:
"你女朋友个数在0-2之间",
"地址在4-64之间",
"密码格式错误",
"email格式错误",
"创建时间不能填",
"你钱包里的钱在0-2之间",
"对象必须是人",
"出生日期比当前时间早",
"冬天温度在0°以下",
"年龄太小了",
"失效时间比当前时间晚",
"url填写错误",
"夏天温度在0°以上",
"不能上来就删除",
"姓名不能为空或空字符串",
"用户名不能为空"
]
3.4 校验通过测试
请求参数:
"createTime":"",
"userName": " ",
"name": "cff",
"age": 11,
"mobile": "13333333333",
"girlFrinds": ["1号","2号"],
"money": 100,
"temperatureWinter": -1,
"temperatureSummer": 12,
"address": "12345",
"people": true,
"delete": false,
"password": "123456",
"email": "11@qq.com",
"expireTime":"2020-11-11",
"birthDate":"2019-11-11",
"url":"http://www.pomit.cn"
返回结果:
"0000"
]
四、级联校验
如果一个对象持有另一个对象的引用,可以使用@Valid注解进行级联校验。 如下所示:
4.1 实体
package com.cff.springbootwork.validator.vo;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserRole {
@NotEmpty(message = "用户名不能为空")
private String userName;
@NotNull(message = "roleId不能为空")
private Integer roleId;
@Valid
private UserInfo userInfo;
}
4.2 测试Web
@RequestMapping(value = "/test1")
public List<String> test1(@Valid @RequestBody UserRole userRole, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errorMsg = bindingResult.getAllErrors().stream().map(s -> s.getDefaultMessage())
.collect(Collectors.toList());
return errorMsg;
return Collections.singletonList("0000");
}
4.3 测试结果
请求数据:
"userName": "",
"roleId": 1,
"userInfo":{
"createTime":"2018-08-09",
"userName": "",
"name": " ",
"age": 9,
"mobile": "123123123",
"girlFrinds": ["1号","2号","3号"],
"money": 101,
"temperatureWinter": 0,
"temperatureSummer": -1,
"address": "12",
"people": false,
"delete": true,
"password": "123",
"email": "11@",
"expireTime":"2019-11-11",
"birthDate":"2020-11-11",
"url":"qwe"
返回结果:
"失效时间比当前时间晚",
"用户名不能为空",
"用户名不能为空",
"你女朋友个数在0-2之间",
"密码格式错误",
"你钱包里的钱在0-2之间",
"姓名不能为空或空字符串",
"url填写错误",
"冬天温度在0°以下",
"对象必须是人",
"email格式错误",
"不能上来就删除",
"年龄太小了",
"夏天温度在0°以上",
"地址在4-64之间",
"创建时间不能填",
"出生日期比当前时间早"
]
五、手动校验
有时候,不用使用@Valid 自动校验,需要手动调起validator进行校验,可以使用
validator.validate(roleInfo);
进行校验:
5.1 实体
package com.cff.springbootwork.validator.vo;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RoleInfo {
@NotNull(message = "roleId不能为空")
private Integer roleId;
@NotEmpty(message = "roleName不能为空")
private String roleName;
}
5.2 测试
Validator(import javax.validation.Validator;) 在SpringBoot中,可以作为bean之间被注入。
@Autowired
Validator validator;
@RequestMapping(value = "/test2")
public List<String> test2(@RequestParam("roleId") Integer roleId, @RequestParam("roleName") String roleName) {
RoleInfo roleInfo = new RoleInfo(roleId, roleName);
Set<ConstraintViolation<RoleInfo>> sets = validator.validate(roleInfo);
if(sets.isEmpty())return Collections.singletonList("0000");
List<String> errorMsg = sets.stream().map(s -> s.getMessage()).collect(Collectors.toList());
return errorMsg;
}
六、分组校验
分组校验就是处理特殊情况下的校验,使不同的调用走不同的校验组。
如,一个对象A持有另一个对象B的引用,对象B中某些字段不想在对象A校验的时候被校验到,可以使用分组校验。
6.1 实体
假设有两个实体:
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserRoleInfo {
@NotEmpty(message = "用户名不能为空")
private String userName;
@NotNull(message = "roleId不能为空")
private Integer roleId;
@Valid
private RoleInfo roleInfo;
}
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RoleInfo {
@NotNull(message = "roleId不能为空", groups=RoleGroup.class)
private Integer roleId;
@NotEmpty(message = "roleName不能为空", groups=RoleGroup.class)
private String roleName;
}
注意,这里的groups必须是接口。接口内容任意,只是个标识而已。
public interface RoleGroup {
}
Default.class(javax.validation.groups.Default)
是默认分组,不需要自己建立.
6.2 测试不带分组
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.cff.springbootwork.validator.vo.RoleGroup;
import com.cff.springbootwork.validator.vo.RoleInfo;
import com.cff.springbootwork.validator.vo.UserRoleInfo;
@RestController
@RequestMapping("/valid")
public class ValidatorRest {
@Autowired
Validator validator;
@RequestMapping(value = "/test3")
public List<String> test3(@RequestParam("roleId") Integer roleId, @RequestParam("userName") String userName,
@RequestParam("roleName") String roleName) {
UserRoleInfo userRoleInfo = new UserRoleInfo();
userRoleInfo.setRoleId(roleId);
userRoleInfo.setUserName(userName);
RoleInfo roleInfo = new RoleInfo(roleId, roleName);
userRoleInfo.setRoleInfo(roleInfo);
Set<ConstraintViolation<UserRoleInfo>> sets = validator.validate(userRoleInfo);
if (sets.isEmpty())
return Collections.singletonList("0000");
List<String> errorMsg = sets.stream().map(s -> s.getMessage()).collect(Collectors.toList());
return errorMsg;
}
结果:
请求参数:
roleId:1
userName:
roleName:
返回结果:
"用户名不能为空"
]
6.2 测试带分组
注意,
Default.class
是默认分组。
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.cff.springbootwork.validator.vo.RoleGroup;
import com.cff.springbootwork.validator.vo.RoleInfo;
import com.cff.springbootwork.validator.vo.UserRoleInfo;
@RestController
@RequestMapping("/valid")
public class ValidatorRest {
@Autowired
Validator validator;
@RequestMapping(value = "/test3")
public List<String> test3(@RequestParam("roleId") Integer roleId, @RequestParam("userName") String userName,
@RequestParam("roleName") String roleName) {
UserRoleInfo userRoleInfo = new UserRoleInfo();
userRoleInfo.setRoleId(roleId);
userRoleInfo.setUserName(userName);
RoleInfo roleInfo = new RoleInfo(roleId, roleName);
userRoleInfo.setRoleInfo(roleInfo);
Set<ConstraintViolation<UserRoleInfo>> sets = validator.validate(userRoleInfo, RoleGroup.class, Default.class);
if (sets.isEmpty())
return Collections.singletonList("0000");
List<String> errorMsg = sets.stream().map(s -> s.getMessage()).collect(Collectors.toList());
return errorMsg;
}
结果:
请求参数:
roleId:1
userName:
roleName:
返回结果:
"roleName不能为空",
"用户名不能为空"
]
七、自定义注解校验
有时候,我们仍需要自定义校验注解,如,我这里定义一个只校验0或1数据的验证器。
7.1 自定义注解
package com.cff.springbootwork.validator.custom;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
* 自定义类校验注解
* 作用于类,用以校验0/1类型数据
* @author cff
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=TypeZeroOneValidator.class)
public @interface ZeroOne {
String message() default "参数有误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
7.2 自定义Validator
package com.cff.springbootwork.validator.custom;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class TypeZeroOneValidator implements ConstraintValidator<ZeroOne, Object> {
@Override
public void initialize(ZeroOne constraintAnnotation) {
@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
if (obj == null)
return true;
int curNum = 0;
if (obj instanceof String) {
String s = (String) obj;
curNum = Integer.parseInt(s);
} else if (obj instanceof Boolean) {
boolean b = ((Boolean) obj).booleanValue();
if (b) {
curNum = 1;
} else if (obj instanceof Long) {
curNum = ((Long) obj).intValue();
} else {
curNum = ((Integer) obj).intValue();
if (curNum == 0 || curNum == 1)
return true;
return false;
}
7.3 测试实体
package com.cff.springbootwork.validator.vo;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import com.cff.springbootwork.validator.custom.ZeroOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RoleInfoZeroOne {
@NotNull(message = "roleId不能为空")
private Integer roleId;
@NotEmpty(message = "roleName不能为空")
private String roleName;
@ZeroOne(message = "deleted只能为0/1")
private Integer deleted;
}
7.4 测试Web
跟普通使用方法一样,无需更改。
@RequestMapping(value = "/test4")
public List<String> test4(@Valid @RequestBody RoleInfoZeroOne roleInfoZeroOne, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errorMsg = bindingResult.getAllErrors().stream().map(s -> s.getDefaultMessage())
.collect(Collectors.toList());
return errorMsg;
return Collections.singletonList("0000");
}
7.5 测试结果
请求参数:
"roleId":1,
"deleted":3,