# SpringBoot使用Validation校验参数 ##

SpringBoot使用Validation校验参数

一、简介

参考

(14条消息) 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知_@decimalmax和@max_YourBatman的博客-CSDN博客

二、常用注解及依赖

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.2.1.Final</version>
        </dependency>

JSR提供的校验注解

@Null 被注释的元素必须为null

@NotNull 被注释的元素必须不为null

@AssertTrue 被注释的元素必须为true

@AssertFalse 被注释的元素必须为false

@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max=,min=) 被注释的元素的大小必须在指定的范围内

@Digits(integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past 被注释的元素必须是一个过去的日期

@Future 被注释的元素必须是一个将来的日期

@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

Hibernate Validator提供的校验注解

@NotBlank(message=) 验证字符串非null,且trim后长度必须大于0

@Email 被注释的元素必须是电子邮箱地址

@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内

@NotEmpty 被注释的字符串的必须非空

@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

三、使用

比较用的多地方在于以下两种

1、在Controller层中,放在模型参数对象前。
当Controller层中参数是一个对象模型时,只有将@Validated或者是@Valid直接放在该模型前,该模型内部的字段才会被
校验(如果有对该模型的字段进行约束的话)。

2、在Controller层中,放在类上。
当一些约束是直接出现在Controller层中的参数前时,只有将@Validated放在类上时,参数前的约束才会生效。

1.初次使用

例子一

在实体类上,添加你需要校验的字段

/**
 * @Description
 * @Author TuiMao
 * @Date 2023/3/15 10:41
 * @Version 1.0
 */
@Data
public class UserInfo {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Length(min = 6,max = 20,message = "密码长度在6-20之间")
    private String password;


    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不合理")
    private String email;

    @NotBlank(message = "年龄不能为空")
    @Size(min = 3,max = 18,message = "年龄应该在3-18之间")
    private String age;
}
    @GetMapping("democ")
    public String democ(@Size(max = 10,min = 5,message = "只能在5-10") String age){

        return age;
    }

假设我此时用postman模拟发送个请求

idea 控制台

就会返回报错信息

另外,需要注意的是,这种参数的校验,需要使用注解,@Validated 声明在类上也就是本文章的 controller 类上,也就是前面提到的第二种

如果想要拿到这个异常并且返回给用户的话,可以考虑写一个自定义异常,来捕获这个异常,获取到异常信息后,返回给用户。

例子二

    @PostMapping("/demo")
    public void demo( @Validated  UserInfo userInfo){
        log.info("demo 接口调用");
    }

假设我此时用postman模拟发送个请求

{
    "usernam":"",
    "password":"",
    "email":"",
    "age":""
}

而我的控制台

明明我各个字段上面 ,我都有注解来做校验,为什么只出现了第一个?(你的校验信息有可能跟我不一样,这个是正常的)

到了这里,其实是 hibernate-validator 的 一个默认的机制

那么我该如果配置这个机制? 我该如何拿到所有的校验信息呢?

@Configuration
public class ValidatorConfig {
//    @Bean
//    public Validator validator(){
//        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
//                .configure()
//                //  addProperty
//                //          第二个参数  true 表示为快速模式   只返回第一个失败信息
//                //                    false 表示普通模式    返回所以的失败信息
//                .addProperty( "hibernate.validator.fail_fast", "false" ) //快速模式
//                .buildValidatorFactory();
//        Validator validator = validatorFactory.getValidator();
//        return validator;
//    }
@Bean
public LocalValidatorFactoryBean getValidatorFactory() {
    LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
    localValidatorFactoryBean.getValidationPropertyMap().put("hibernate.validator.fail_fast", "false");
    return localValidatorFactoryBean;
}

}

在这里进行 配置,注释的代码,和没有注释的代码,其实效果都是一样的,改个值就行了

在进行测试!

2023-03-15 22:17:52.795 WARN 15948 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errorsField error in object 'userInfo' on field 'email': rejected value [null]; codes [NotBlank.userInfo.email,NotBlank.email,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.email,email]; arguments []; default message [email]]; default message [邮箱不能为空]Field error in object 'userInfo' on field 'password': rejected value [null]; codes [NotBlank.userInfo.password,NotBlank.password,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.password,password]; arguments []; default message [password]]; default message [密码不能为空]Field error in object 'userInfo' on field 'username': rejected value [null]; codes [NotBlank.userInfo.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.username,username]; arguments []; default message [username]]; default message [用户名不能为空]]

这样就可以拿到所有的错误信息了

那么?我是否可以在拿到相对来说,美观一些的错误信息,来给到用户呢?

    @PostMapping("/demo")
    public String demo( @Validated UserInfo userInfo,BindingResult bindingResult){
        log.info("demo 接口调用");
        if(bindingResult.hasErrors()){
            String result = "";
            System.out.println("有异常信息");
            for (ObjectError error : bindingResult.getAllErrors()) {
                String defaultMessage = error.getDefaultMessage();
                result = result + defaultMessage +",";
            }
                return result;
        }
        return userInfo.toString();
    }

此时调用的话,返回的就是 用户名不能为空,邮箱不能为空,密码不能为空

如果不采用BindingResult来容纳异常信息时,那么异常会被向外抛出。注解校验不通过时,可能抛出的
异常有BindException异常、ValidationException异常(或其子类异常)、
MethodArgumentNotValidException异常。

2.分组

首先,我得简述下分组的用途,假设,我现在的业务是一个User用户,这个实体类有CRUD ,我需要对新增和修改时候,传入的参数有限制,两个情况下的参数有不同的限制,这个时候,就适合用分组

1、定义分组

public class ValidGroup {

    // 新增使用(配合spring的@Validated功能分组使用)
    public interface Insert{}

    // 更新使用(配合spring的@Validated功能分组使用)
    public interface Update{}

    // 删除使用(配合spring的@Validated功能分组使用)
    public interface Delete{}

    // 属性必须有这两个分组的才验证(配合spring的@Validated功能分组使用)
    @GroupSequence({Insert.class, Update.class,Delete.class})
    public interface All{}
}

2.分组的使用

@Data
public class groupUser {
    //只能在Delete和Update的时候才能够进行生效.
    @Min(value = 1,message = "ID不能小于1",groups = {ValidGroup.Delete.class,ValidGroup.Update.class})
    private int id;

    @NotBlank(message = "用户名不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
    private String username;

    @NotBlank(message = "密码不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
    @Length(min = 6,max = 20,message = "密码长度在6-20之间",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
    private String password;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不合理")
    private String email;

}
  /* ********************************** group 分组  ****************************  */
    @RequestMapping("/saveUserInfo")
    public groupUser saveUserInfo(@Validated({ValidGroup.Insert.class}) groupUser userInfo){
        //save userInfo:将userInfo进行保存
        //userInfoService.save(userInfo);
        return userInfo;
    }

    @RequestMapping("/updateUserInfo")
    public groupUser updateUserInfo(@Validated({ValidGroup.Update.class})   groupUser userInfo){
        //save userInfo:将userInfo进行保存
        //userInfoService.update(userInfo);
        return userInfo;
    }

    @RequestMapping("/deleteUserInfo")
    public groupUser deleteUserInfo(@Validated({ValidGroup.Delete.class}) groupUser userInfo){
        //save userInfo:将userInfo进行保存
        //userInfoService.delete(userInfo);
        return userInfo;
    }

http://127.0.0.1:8080/userInfo/saveUserInfo?username=wuqian&password=1234456&email=aa@qq.com

说明:保存的方法上并没有指定具体的分组,所以生效的配置只有email,所以在保存的时候以下的链接就能保存成功了。

http://127.0.0.1:8080/userInfo/saveUserInfo?email=aa@qq.com

这里是举例了个错误的例子,大家自行进行修改哦。

修改数据:

http://127.0.0.1:8080/userInfo/updateUserInfo?username=wuqian&password=1234456&id=1

说明:这里的核心是id必须配置,由于email没有在分组内,所以根本不会进行生效。

删除数据:

http://127.0.0.1:8080/userInfo/deleteUserInfo?id=1

说明:删除只需要id。

3.嵌套校验

@Data
public class UserModel implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1906142335911461001L;
	
	private String id;
	
	@NotNull(message = "姓名不能为空")
	@Length(min = 1, max = 20, message = "姓名为1到20个字符之间")
	@Pattern(regexp="^[\\u4e00-\\u9fa5]{0,}$", message="姓名为汉字字符")
	private String name;
	
	@Pattern(regexp="^[A-Za-z]+$", message="英文名称只能包含字母")
	@Length(min = 1, max = 20, message = "英文名称为1到20个字符之间")
	private String englishName;
	
	@Range(min = 1, max = 200, message = "年龄范围为1到200")
	private Integer age;
	
	@NotBlank(message="性别不能为空")
	@Pattern(regexp="^(0|1)$", message="性别不正确")
	private String gender; 
	
	@AssertFalse(message = "必须为false")
    private Boolean isFalse;
	
	@AssertTrue(message = "必须为true")
    private Boolean isTrue;

	@Range(min = 1, max = 500, message = "体重范围为1到500kg")
	private Float weight;
	
	@Range(min = 1, max = 300, message = "年龄范围为1到300cm")
	private Float tall;
	
	@NotNull(message = "身份证不能为空")
	@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份证格式错误")
	private String cardId;
	
	@Pattern(regexp="^((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))$", message="ip格式不正确")
	private String ip;
	
	@Length(min = 0, max = 200, message = "工作内容不能超过200")
	@Pattern(regexp="^http[s]*://[^\\s]*$", message="个人网站地址格式不正确")
	private String website;
	
	@Length(min = 4, max = 20, message = "qq号码不能超过20")
	@Pattern(regexp="^[1-9][0-9]{4,}$", message="qq号是从1000开始")
	private String qqNum;
	
	@NotBlank(message="userName不能为空")
    @Length(max=6, min=3, message="userName最小3位,最大6位")
    private String userName;

    @NotBlank(message="密码不能为空")
    @Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message="密码必须是8~10位数字和字母的组合")
    private String password;
    
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message="手机号格式不正确")
    private String cellphone;
    
    @Pattern(regexp="^\\d{3}-\\d{8}|\\d{4}-\\d{7}$", message="固定电话格式不正确")
    private String telephone;

    @Email(message="邮箱格式不正确")
    @Pattern(regexp="^([\\w-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([\\w-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$", message="邮箱格式不正确")
    private String email;
    
    @Past
    private Date bornDate;
    
    @Future
    private Date future;
	
    @Length(min = 0, max = 200, message = "地址不能超过200")
	private String address;
    
    @Pattern(regexp="^[1-9]\\d{5}(?!\\d)$", message="邮政编码格式不正确")
    private String postCode;


    /**
     *  关于这样的嵌套的校验
	 *  只需要在属性的上方加上个  @Valid 即可
	 *  包括 list 也同样适用
     */
	@Valid
	private UserModel father;
	
	@Valid
	private UserModel mother;
	
	@Length(min = 0, max = 200, message = "工作内容不能超过200")
	private String work;
}
	@PostMapping("/testPost")
	public Object testPost(@RequestBody @Valid UserModel userModel, BindingResult result){  
        StringBuilder sb = new StringBuilder();
		if(result.hasErrors()){    
			for(ObjectError error:result.getAllErrors()){       
			   sb.append(error.getDefaultMessage()); 
			   sb.append(";"); 	
			} 	
             return sb.toString();   					    
		}  
		return userModel;
	}

post man json 数据测试

{
	"id":"asfda546a6565565e6s568d79",
	"name":"张小三",
	"age":29,
	"cardId":"wererewrewttwwte545",
	"gender":"0",
	"isFalse":false,
	"isTrue":true,
	"weight":72.3,
	"tall":172,
	"ip":"192.168.1.101",
	"website":"http://www.zhangxiaosan.cn",
	"qqNum":"694434544",
	"userName":"tianyamingyuedao",
	"password":"sanquanbuliugen",
	"cellphone":"13845442123",
	"telephone":"010-43564234",
	"email":"2345435345@qq.com",
	"bornDate":"1992-07-17",
	"future":"2088-07-17",
	"address":"北京市西城区xxxx",
	"postCode":"100010",
	"work":"xxx公司",
	"father":{
		"id":"asfda546a6565565e6s568d79",
		"name":"张老三",
		"age":61,
		"cardId":"wererewrewttwwte545",
		"gender":"0",
		"isFalse":false,
		"isTrue":true,
		"weight":72.3,
		"tall":172,
		"ip":"192.168.1.101",
		"website":"http://www.张老三.cn",
		"qqNum":"694434544",
		"userName":"zhanglaosan",
		"password":"sanquanbuliugen",
		"cellphone":"13845442123",
		"telephone":"010-43564234",
		"email":"6944345@qq.com",
		"bornDate":"1960-07-17",
		"future":"2060-07-17",
		"address":"北京市西城区xxxx",
		"postCode":"100010"
	},
	"mother":{
		"id":"asfda546a6565565e6s568d79",
		"name":"陈小六",
		"age":57,
		"cardId":"wererewrewttwwte545",
		"gender":"1",
		"isFalse":false,
		"isTrue":true,
		"weight":72.3,
		"tall":172,
		"ip":"192.168.1.101",
		"website":"http://www.chenxiaoliu.cn",
		"qqNum":"694434544",
		"userName":"chenxiaoliu",
		"password":"sanquanbuliugen",
		"cellphone":"118245442123",
		"telephone":"010-43564234",
		"email":"6944345@qq.com",
		"bornDate":"1964-07-17",
		"future":"2060-07-17",
		"address":"北京市西城区xxxx",
		"postCode":"100010"
	}
}

控制台输出

javax.validation.ConstraintViolationException: testPost.userModel.userName: userName最小3位,最大6位, testPost.userModel.father.cardId: 身份证格式错误, testPost.userModel.father.password: 密码必须是8~10位数字和字母的组合, testPost.userModel.mother.cellphone: 手机号格式不正确, testPost.userModel.mother.cardId: 身份证格式错误, testPost.userModel.mother.userName: userName最小3位,最大6位, testPost.userModel.father.userName: userName最小3位,最大6位, testPost.userModel.cardId: 身份证格式错误, testPost.userModel.mother.password: 密码必须是8~10位数字和字母的组合, testPost.userModel.password: 密码必须是8~10位数字和字母的组合

另外一种是只需在Controller中方法参数需要校验的实体上添加@Valid注解:

	@PostMapping("/testPost2")
	public Object testPost2(@RequestBody @Valid UserModel userModel){  
		...
		return userModel;
	}

但需要在启动类所在包下创建控制器切面类,并创建方法捕捉MethodArgumentNotValidException异常,在该方法中统一处理校验失败信息:

@RestControllerAdvice
@Component
public class GlobalExceptionAdvice {
 
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public WrappedResult validException(MethodArgumentNotValidException e) {
    //验证post请求的参数合法性
    MethodArgumentNotValidException notValidException = e;
    BindingResult result = notValidException.getBindingResult();
    StringBuilder sb = new StringBuilder();
    if(result.hasErrors()){    
		for(ObjectError error:result.getAllErrors()){  
			sb.append(error.getDefaultMessage()); 
			sb.append(";"); 
			
		} 						    
	}  
    String msg = sb.toString();
    //WrappedResult是自定义返回结果包装类,用户可以使用自己的工具类
    return WrappedResult.failedWrappedResult(msg);
  }
}

4.使用工具类获取校验信息

package com.validation.util;
 

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
 
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validation;
import java.util.Set;
 
@Slf4j
public class ValiUtil {
 
    /**
     * 校验对象
     * 返回一个错误
     *
     * @param t   对象
     * @param <T> t
     */
    public static <T> void validateBySingleError(@Valid T t, Class<?> clazz) {
        Set<ConstraintViolation<@Valid T>> validateSet = Validation.buildDefaultValidatorFactory()
                .getValidator()
                .validate(t, clazz);
        if (!CollectionUtils.isEmpty(validateSet)) {
            for (ConstraintViolation<@Valid T> vali : validateSet) {
//                throw new ApplicationException(vali.getMessage());
                log.info(vali.getPropertyPath() + "-----" + vali.getMessage());
            }
 
        }
    }
 
    /**
     * 校验对象
     * 返回全部
     *
     * @param t   对象
     * @param <T> t
     */
    public static <T> void validateByAllError(@Valid T t, Class<?> clazz) {
        Set<ConstraintViolation<@Valid T>> validateSet = Validation.buildDefaultValidatorFactory()
                .getValidator()
                .validate(t, clazz);
        if (!CollectionUtils.isEmpty(validateSet)) {
            String messages = validateSet.stream()
                    .map(ConstraintViolation::getMessage)
                    .reduce((m1, m2) -> m1 + "\n" + m2)
                    .orElse("参数输入有误!");
//            throw new ApplicationException(messages);
            log.info(messages);
        }
    }
 
}
   @PostMapping("/demoOne")
    public void demoOne( UserInfo userInfo){
        log.info("demoOne 接口调用");
        log.info("=================== Create  ===================");
        ValiUtil.validateBySingleError(userInfo, Create.class);
        log.info("=================== Update  ===================");
        ValiUtil.validateBySingleError(userInfo, Update.class);
        log.info("=================== Default  ===================");
        ValiUtil.validateBySingleError(userInfo, Default.class);
    }
{
    "username":"1",
    "password":"",
    "email":"",
    "age":""
}

控制台

roller : demoOne 接口调用
2023-03-16 22:09:35.481 INFO 13548 --- [nio-8080-exec-2] c.v.controller.UserInfoController : =================== Create ===================
2023-03-16 22:09:35.517 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : age-----年龄不能为空
2023-03-16 22:09:35.517 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : username-----用户名不能为空
2023-03-16 22:09:35.517 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : password-----密码不能为空
2023-03-16 22:09:35.517 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : email-----邮箱不能为空
2023-03-16 22:09:35.517 INFO 13548 --- [nio-8080-exec-2] c.v.controller.UserInfoController : =================== Update ===================
2023-03-16 22:09:35.524 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : age-----年龄不能为空
2023-03-16 22:09:35.524 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : username-----用户名不能为空
2023-03-16 22:09:35.524 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : password-----密码不能为空
2023-03-16 22:09:35.524 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : email-----邮箱不能为空
2023-03-16 22:09:35.524 INFO 13548 --- [nio-8080-exec-2] c.v.controller.UserInfoController : =================== Default ===================
2023-03-16 22:09:35.530 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : age-----年龄不能为空
2023-03-16 22:09:35.530 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : username-----用户名不能为空
2023-03-16 22:09:35.530 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : password-----密码不能为空
2023-03-16 22:09:35.530 INFO 13548 --- [nio-8080-exec-2] com.validation.util.ValiUtil : email-----邮箱不能为空

posted @ 2023-03-16 22:15  浩楠要秃顶  阅读(180)  评论(0编辑  收藏  举报