Spring Boot - 参数校验
前言
数据的校验是网站一个不可或缺的功能,前端的 js 校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用 http 工具直接向后端请求一些违法数据,服务端的数据校验也是必要的。
所以我们可以在 Controller 进行参数校验,只有通过了校验,才能进入 Controller 的方法里。
Controller 中方法参数校验示例
使用 @Valid 或 @Validated 注解。
Maven 依赖
@Valid:新版本的springBoot需要手动引入下面的依赖,老版本只需引入spring-boot-starter-web即可,里面集成了Hibernate-Validator
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>@Validated:是对hibernate-validator的封装,是spring提供的校验机制,只要引入spring-context依赖即可
实体类字段加上相关注解
public class User implements Serializable {
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Range(min = 0, max = 200, message = "年龄不合法")
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// setter,getter 方法省略......
}@NotBlank:只用于字符串,字符串不能为 null,并且去除两端空白字符后的长度大于 0,例:""、" "@NotEmpty:只用于字符串、集合、map、数组,且不能为 null,并且长度或者大小大于 1@NotNull:适用于所有类型,且不能为 null@AssertTrue:被注释的元素必须为 true@AssertFalse:被注释的元素必须为 false@Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max,min):被注释的元素的大小必须在指定的范围内@Email:被注释的元素必须是电子邮件地址@Length:被注释的字符串的大小必须在指定的范围内@Range:被注释的元素必须在合适的范围内
注意:如果是 String 类型,使用 @NotBlank 而不是 NotNull 或者 @NotEmpty,如果字符串是 "",两个后者无法判断为空。
@Valid 或 @Validated 注解
在 Controller 方法参数加上 @Valid 或 @Validated 注解
@Controller
public class UserController {
@RequestMapping(path = "validatorUser", method = RequestMethod.POST)
@ResponseBody
public ResponseResult validatorUser(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 用于获取相应字段上添加的 message 中的内容
String message = bindingResult.getFieldError().getDefaultMessage();
return new ResponseResult(500, message, null);
}
return new ResponseResult(200, "成功", user);
}
}多个参数校验
public Objet test(@Validated Object param1, BindingResult result1 ,@Validated Object param2, BindingResult Result2) {
// ......
}@Validated 注解特有的功能
@Valid 不具备的功能,而注解 @Validated 注解特有的功能:分组校验。源码分别如下:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
Class<?>[] value() default {};
}
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
}参数分组校验
当一个实体类需要多种验证方式时,例如:对于一个实体类的 id 来说,新增的时候是不需要的,对于更新时是必须的。可以通过 groups 对验证进行分组。
分组接口(空接口)
通过向 groups 分配不同类的 class 对象,达到分组目的
public interface UserServiceInsert {
}
public interface UserServiceUpdate {
}实体类
public class UserInfo implements Serializable {
@Null(message = "新增时id必须为空", groups = {UserServiceInsert.class})
@NotNull(message = "更新时id不能为空", groups = {UserServiceUpdate.class})
@NotNull(message = "删除时 id 不允许为空", groups = UserServiceDelete.class)
private Integer id;
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Range(min = 0, max = 200, message = "年龄不合法")
private Integer age;
public UserInfo(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
// setter,getter 方法省略......
public interface UserServiceInsert {
}
public interface UserServiceUpdate {
}
public interface UserServiceDelete {
}
}Controller 控制类
@Controller
public class UserController {
@RequestMapping(path = "insertUser", method = RequestMethod.POST)
@ResponseBody
public ResponseResult insertUser(@Validated(value = UserServiceInsert.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String message = bindingResult.getFieldError().getDefaultMessage();
return new ResponseResult(500, message, null);
}
return new ResponseResult(200, "成功", userInfo);
}
@RequestMapping(path = "updateUser", method = RequestMethod.POST)
@ResponseBody
public ResponseResult updateUser(@Validated(value = UserServiceUpdate.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String message = bindingResult.getFieldError().getDefaultMessage();
return new ResponseResult(500, message, null);
}
return new ResponseResult(200, "成功", userInfo);
}
}@Validated添加了groups属性时,其只会校验实体分组的属性。如只会校验UserInfo中的id属性,而不会校验name,age属性- 不加
groups时,不会校验有groups的属性
嵌套校验
@Valid 加在方法参数时,不会自动进行嵌套验证,而是用在需要嵌套验证类内的相应字段上,来配合方法参数上 @Validated 或 @Valid 来进行嵌套验证
实体类
public class Teacher implements Serializable {
@NotEmpty(message = "Teacher 姓名不能为空")
private String teacher_name;
@NotNull(message = "Teacher 年龄不能为空")
@Range(min = 0, max = 200, message = "Teacher 年龄不合法")
private Integer teacher_age;
@Valid
@Size(min = 1, max = 10, message = "列表中的元素数量为1~10")
private List<Student> students;
public Teacher(String teacher_name, Integer teacher_age, List<Student> students) {
this.teacher_name = teacher_name;
this.teacher_age = teacher_age;
this.students = students;
}
// setter,getter 方法省略......
}
public class Student implements Serializable {
@Valid
@NotEmpty(message = "Student 姓名不能为空")
private String name;
@Valid
@NotNull(message = "Student 年龄不能为空")
@Range(min = 0, max = 200, message = "Student 年龄不合法")
private Integer age;
public Student(String name, Integer age) {
this.age = age;
this.name = name;
}
// setter,getter 方法省略......
}Controller 控制类
@Controller
public class UserController {
@RequestMapping(path = "nestValid", method = RequestMethod.POST)
@ResponseBody
public ResponseResult nestValid(@Validated @RequestBody Teacher teacher, @NotNull BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
String message = bindingResult.getFieldError().getDefaultMessage();
return new ResponseResult(500, message, null);
}
return new ResponseResult(200, "成功", teacher);
}
}对象集合校验
如果 Controller 的参数是一个 List 集合的对象,则该对象不会执行校验,如下:
@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody List<GenericCategory> genericCategory) {
GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
return HttpResult.okOrFail(category);
}参数是 List<GenericCategory> genericCategory,而 GenericCategory 实体类里的校验是不会起效的,这是 List 导致的,那么如何解决呢?
既然 @Validated 只能对单个实体类校验,那么我们就自定义一个实体类,实现集合的效果,如下:
// @Data 和 @NoArgsConstructor 是 Lombok 带有的,自动生成 setter、getter 等方法,生成无参构造器
@Data
@NoArgsConstructor
public class ValidList<E> implements List<E> {
@Valid
private List<E> list = new LinkedList<>();
public ValidList(List<E> paramList) {
this.list = paramList;
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(0);
}
@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 的封装类,封装了大部分常用方法,如果没有你需要的方法,重写就行。
使用:
@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody ValidList<GenericCategory> genericCategory) {
GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
return HttpResult.okOrFail(category);
}将 List 改成 ValidList 即可。
控制分组校验顺序
@GroupSequence 它是 JSR 标准提供的注解,可以按指定的分组先后顺序进行验证;前面的分组校验不通过,后面的分组校验就不执行。
- 如:
@GroupSequence({One.class, Two.class, Three.class})先执行One分组校验,然后执行Two分组校验。如果One分组校验失败了,则不会进行Two分组的校验。即必须第一个组校验正确了,才执行第二组校验
@Data
@GroupSequence({One.class, Two.class, UserVO.class})
public class UserVO {
@NotNull(message = "姓名不能为空", groups = {One.class})
@Size(min = 1, max = 10, message = "姓名的长度在1-10之间", groups = {Two.class})
private String name;
@NotNull(message = "年龄不能为空", groups = {One.class})
@Min(value = 1, message = "年龄不能小于1岁", groups = {Two.class})
@Max(value = 200, message = "年龄不能大于200岁", groups = {Two.class})
private Integer age;
@NotNull(message = "性别不能为空", groups = {One.class})
@Min(value = 0, message = "性别取值不能小于0", groups = {Two.class})
@Max(value = 1, message = "性别取值不能大于1", groups = {Two.class})
private Integer sex;
public interface One {}
public interface Two {}
public interface UserVO {}
}测试接口
@PostMapping("/test/group")
public String userVO(@RequestBody @Validated UserVO form) {
return "success";
}@Validated 类和参数区别
@Validated 可以在 Controller 类上添加,也可以在方法的参数里添加,那么两者区别是什么?
- 作用在方法上,用于校验实体类里的规则
- 作用在类上,用于校验类的方法参数规则
注意:如果方法的参数是实体类,那么作用在类上,则不会校验,需要同时在方法里添加 @Validated。
那么 @Validated 作用在类上的作用是什么呢?
能够校验基本类型的参数,如:
@RestController
@Validated
public class GenericUserController {
@PostMapping("/updateGenericUserRole")
public Response updateGenericUserRole(@NotBlank(message = "无效的参数") String username, @NotNull(message = "无效的参数") Integer projectId, @NotBlank(message = "无效的参数") String roleCode) {
boolean isSuccess = genericUserService.updateGenericUserRole(username, projectId, roleCode);
if (!isSuccess) {
return HttpResult.fail("更新角色失败");
}
return HttpResult.ok("更新角色成功");
}
}此时 username、projectId、roleCode 的校验就会生效。
如果参数是实体类,则不会校验规则。
@RestController
@Validated
public class GenericUserController {
@PostMapping("/test/group")
public String userVO(@RequestBody UserVO form) {
return "success";
}
}UserVO 里的规则不会起效果,所以我们需要单独加上 @Validated。
@RestController
@Validated
public class GenericUserController {
@PostMapping("/test/group")
public String userVO(@Validated @RequestBody UserVO form) {
return "success";
}
}自定义校验注解
业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验注解来满足我们的需求。
注解 1
如: 添加一个用于校验字符串不能包含空格的校验注解 @CanNotHaveBlank。
自定义校验注解,并且通过
validatedBy指定了这个注解真正的验证类java/** * 参考系统提供的校验注解可知,message,groups,payload 这 3 个属性必须提供 * @Constraint(validatedBy = CanNotHaveBlankValidator.class) 指定该注解的校验类 */ @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented //指定了当前注解使用哪个校验类来进行校验。 @Constraint(validatedBy = {CanNotHaveBlankValidator.class}) public @interface CanNotHaveBlank { // 默认错误消息 String message() default "不能包含空格"; // 分组 Class<?>[] groups() default {}; // 负载 Class<? extends Payload>[] payload() default {}; // 指定多个时使用 @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { CanNotHaveBlank[] value(); } }
注意: message 用于显示错误信息这个字段是必须的,groups 和 payload 也是必须的。
@Constraint(validatedBy = {HandsomeBoyValidator.class}) 用来指定处理这个注解逻辑的类。
编写验证类
CanNotHaveBlankValidator- 所有的验证类都需要实现
ConstraintValidator<注解类型,校验bean类型>接口,实现该接口必须指定对应的注解类型以及校验 Bean 的类型,接口包含一个初始化事件方法initialize,和一个判断是否合法的方法isValid isValid()中的ConstraintValidatorContext包含了认证中所有的信息,可以利用这个上下文获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作请附上原文出处链接及本声明
javapublic interface ConstraintValidator<A extends Annotation, T> { default void initialize(A constraintAnnotation) { } boolean isValid(T var1, ConstraintValidatorContext var2); }javapublic class CanNotHaveBlankValidator implements ConstraintValidator<CanNotHaveBlank, String> { /** * 初始化获取注解的值 * @param constraintAnnotation */ @Override public void initialize(CannotHaveBlank constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); } /** * 初始化获取注解的值 * @param value 要校验的字段值 * @param context 校验上下文 * @return true 校验通过、false 校验失败 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { // null 时不进行校验 if (value != null && !value.contains(" ")) { // 获取默认提示信息 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); // 禁用默认提示信息 context.disableDefaultConstraintViolation(); // 设置提示语 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false; } return true; } }- 所有的验证类都需要实现
CannotHaveBlank 是自己定义的注解。
表单数据
java@Data public class CustomForm { // 电话号码 @CanNotHaveBlank private String phone; }
注解 2
对状态的校验: status: 1 或者 2.
自定义注解:
java/** * 使用方式: * @IncludeValid(value = {"0","1"}, message = "状态值必须为 0, 1") * * * 校验值是否为指定的值 * */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.PARAMETER}) @Constraint(validatedBy = IncludeValidatorClass.class) public @interface IncludeValid { String[] value() default {}; String message() default "flag is not found"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }具体的逻辑校验
java/** * 校验值是否为指定的值, 处理类 * */ public class IncludeValidatorClass implements ConstraintValidator<IncludeValid, Integer> { private String[] values; @Override public void initialize(IncludeValid constraintAnnotation) { this.values = constraintAnnotation.value(); } @Override public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) { boolean isValid = false; if (null == value) { return true; } for (int i = 0; i < values.length; i++) { if (values[i].equals(String.valueOf(value))) { isValid = true; break; } } return isValid; } }使用方式
java@NotEmpty(message = "用户名不能为空") @ApiModelProperty("用户名") private String name; // 自定义的校验 @IncludeValid(value = {"1", "2"}, message = "用户的状态不正确") private Integer status;
注解 3
手机号码验证
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {
boolean required() default true;
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}逻辑校验
public class MobileValidator implements ConstraintValidator<IsMobile, String>{
@Override
public void initialize(IsMobile constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 关于手机号的验证偷个懒 不为空且长度不是 11 位的字符串 就被认为是非法
if(value != null && value.length() != 11) {
System.out.println("手机号非法");
return false;
}
return true;
}
}使用
@Data
public class User {
// ...
@IsMobile(message = "手机号格式非法")
private String mobile;
}自定义分组校验@GroupSequenceProvider
@GroupSequence只能在类中事先定义校验分组的顺序。
- 如遇到这种需求: 当 type 值为 A,paramA 值必传。type 值为 B,paramB 值必须传,单独使用分组校验和控制分组校验顺序都无法满足需求。需要使用
@GroupSequenceProvider
定义校验类
@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroupForm {
// 类型
@Pattern(regexp = "[A|B]", message = "类型不必须为 A|B")
private String type;
//参数 A
@NotEmpty(message = "参数A不能为空", groups = {WhenTypeIsA.class})
private String paramA;
// 参数 B
@NotEmpty(message = "参数B不能为空", groups = {WhenTypeIsB.class})
private String paramB;
// 分组 A
public interface WhenTypeIsA { }
// 分组 B
public interface WhenTypeIsB { }
}实现 DefaultGroupSequenceProvider 接口,编写分组校验逻辑
public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> {
@Override
public List<Class<?>> getValidationGroups(CustomGroupForm form) {
List<Class<?>> defaultGroupSequence = new ArrayList<>();
// 默认分组
defaultGroupSequence.add(CustomGroupForm.class);
// 如果类型值为 A 使用 A 分组 WhenTypeIsA
if (form != null && "A".equals(form.getType())) {
defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class);
}
// 如果类型值为 B 使用 B 分组 WhenTypeIsB
if (form != null && "B".equals(form.getType())) {
defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class);
}
// 返回分组
return defaultGroupSequence;
}
}手动校验
在某些场景下需要我们手动校验 bean,用校验器对需要被校验的 bean 发起 validate 获得校验结果
- 既可以使用
Hibernate Validation提供Validator,也可以使用Spring Validation的Validator
使用
Hibernate Validation提供Validator
public class ValidationTest {
public static void main(String[] args) {
Foo foo = new Foo();
foo.setUsername(null);
foo.setPassword(null);
foo.setUserType("");
// 构建 Validator
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
// 使用 Validator 校验 bean
Set<ConstraintViolation<Foo>> set = validator.validate(foo);
for (ConstraintViolation<Foo> constraintViolation : set) {
System.out.println(constraintViolation.getMessage());
}
}
@Data
public static class Foo {
@NotNull(message = "username不能为空")
private String username;
@NotNull(message = "password不能为空")
private String password;
@NotBlank(message = "userType不能为空")
private String userType;
}
}使用
Spring Validation的Validator
也可以使用配置类初始化 LocalValidatorFactoryBean,然后从 Spring 容器中获取
@Component
@Configuration
public class GlobalWebConfig {
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
}如果使用 springBoot,LocalValidatorFactoryBean 已经成为了 Validator 的默认实现,使用时只需要自动注入即可
@Autowired
Validator globalValidator;工具类 ValidatorUtils
/**
* 为什么要使用这个工具类呢?
* 1、controller方法中不用加入BindingResult参数
* 2、controller方法中需要校验的参数也不需要加入@Valid或者@Validated注解
* <p>
* 具体使用
* 在controller方法或者全局拦截校验器中调用 ValidatorUtils.validateResultProcess(需要校验的Bean) 直接获取校验的结果。
*
**/
@Component
public class ValidatorUtils implements ApplicationContextAware {
//jackson的对象映射类
private static final ObjectMapper objectMapper = new ObjectMapper();
private static Validator validator;
/*
* 校验bean并返回所有验证失败信息
* @param obj 当前校验对象
* @param groups 当前校验的组,非必传,不传按照默认分组校验
* @return 如: Optional[[{"propertyPath":"Foo.password","message":"password为NULL"},{"propertyPath":"Foo.userType","message":"userType为BLANK"}]]
* @throws ServiceException
*/
public static Optional<String> validateResultProcess(Object obj, Class<?>... groups) throws ServiceException {
// 用验证器执行验证,返回一个验证失败的set集合
Set<ConstraintViolation<Object>> results = validator.validate(obj,groups);
// 判断是否为空,空:说明验证通过,否则就验证失败
if (CollectionUtils.isEmpty(results)) {
return Optional.empty();
}
List<ErrorMessage> errorMessages = results.stream()
//将results转换成 List<ErrorMessage>返回
.map(result -> {
try {
List<ErrorMessage> childErrorMessages = objectMapper.readValue(result.getMessage(), new TypeReference<List<ErrorMessage>>() {
});
return childErrorMessages;
} catch (Exception e) {
ErrorMessage errorMessage = new ErrorMessage();
errorMessage.setPropertyPath(String.format("%s.%s", result.getRootBeanClass().getSimpleName(), result.getPropertyPath().toString()));
errorMessage.setMessage(result.getMessage());
return Arrays.asList(errorMessage);
}
})
//合并 map操作转换成的多个 List<ErrorMessage>为一个
.flatMap(errorMessageList -> errorMessageList.stream())
.collect(Collectors.toList());
try {
return Optional.of(objectMapper.writeValueAsString(errorMessages));
} catch (JsonProcessingException e) {
throw new ServiceException("JsonProcessingException " + e.getMessage());
}
}
/**
* 校验bean校验失败抛出自定义异常 ServiceException
* @param obj 当前校验对象
* @param groups 当前校验的组,非必传,不传按照默认分组校验
* @throws ServiceException
*/
public static void validateResultProcessWithException(Object obj, Class<?>... groups) throws ServiceException {
Optional<String> validateResult = ValidatorUtils.validateResultProcess(obj,groups);
if (validateResult.isPresent()) {
throw new ServiceException(validateResult.get());
}
}
/**
* 初始化validator 对象
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取Hibernate validator 的 validator
//ValidatorUtils.validator = Validation.buildDefaultValidatorFactory().getValidator();
//通过Spring 封装的 LocalValidatorFactoryBean获取validator
ValidatorUtils.validator = (Validator) applicationContext.getBean("validator");
/*
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
*/
}
/**
* 校验分组
*/
public static class ValidatorGroup {
public interface First extends Default { }
public interface Second extends Default { }
public interface Third extends Default { }
}
/**
* 错误信息封装
*/
public static class ErrorMessage {
private String propertyPath;
private String message;
public String getPropertyPath() { return propertyPath; }
public void setPropertyPath(String propertyPath) { this.propertyPath = propertyPath; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
}使用 ValidatorUtils 校验 bean
public void test() {
// 新增时 id 必须为空
UserForm addForm = new UserForm();
addForm.setId("1");
addForm.setName("张三");
addForm.setAge(12);
// 校验 bean 并返回所有验证失败信息
Optional<String> addResult = ValidatorUtils.validateResultProcess(addForm, One.class);
System.out.println(addResult);
// 更新时id不能为空
UserForm updateForm = new UserForm();
updateForm.setId(null);
addForm.setName("张三");
updateForm.setAge(12);
// 校验 bean 并返回所有验证失败信息
Optional<String> updateResult = ValidatorUtils.validateResultProcess(updateForm, Two.class);
System.out.println(updateResult);
}返回结果
Optional[[{"propertyPath":"UserForm.id","message":"新增时id必须为空"}]]
Optional[[{"propertyPath":"UserForm.id","message":"更新时id不能为空"}]]参数校验常用注解
| 注解 | 说明 |
|---|---|
@Null | 限制只能为 null |
@NotNull | 限制必须不为 null |
@NotEmpty | 验证注解的元素值 不为 null 且不为空(字符串长度不为 0、集合大小不为 0)(主要用于:String,Collection,Map,array) |
@NotBlank | 只支持字符串类型字段,验证注解的元素值 不为空(不为 null、去除首位空格后长度为 0),不同于 @NotEmpty,@NotBlank 只应用于字符串,且在比较时会去除字符串的空格 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在 min 到 max 之间,(主要用于:String,Collection,Map and array) |
@Range(min, max) | 被注释的元素必须在合适的范围内(主要用于:BigDecimal,BigInteger,String,byte,short,int,long,原始类型的包装类) |
@Length(min, max) | 被注解的对象必须是字符串,大小必须在制定的范围内 |
| @DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
| @DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
| @Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction |
| @AssertFalse | 限制必须为 false |
| @AssertTrue | 限制必须为 true |
| @Future | 限制必须是一个将来的日期 |
| @Past | 验证注解的元素值(日期类型)比当前时间早 |
@Email | 验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 Email 格式 |