springboot中注解校验@Valid@Validated(亲测有效)

1.bean validation校验框架说明

2.常用校验方式

2.1 参数为对象校验方式(@RequestBody+@Vallid)

2.2 参数为单个参数校验方式

2.3 分组校验(公共对象参数,对于指定参数,有的接口需要校验,有的接口不需要校验的场景)

2.4 级联校验

2.5 自定义注解

2.5.1 自定义注解进行关联校验

2.5.2 同一个自定义注解对多个对象进行相同关联校验

3.校验源码说明

1.bean validation校验框架说明

        JSR,Java Specification Requests 的缩写,意思是 Java 规范提案。JSR-303 是JAVA EE 6 中的一项子规范,叫做 Bean Validation。Hibernate Validator 是 Bean Validation接口规范 的实现之一。

         @Valid与@Validated区别:后者是前者的补充,前者能做的后者都能做,并且后者支持分组、级联操作等。

2.常用校验方式

需要引入依赖:

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

2.1 参数为对象校验方式

1.校验步骤说明

1.请求对象上添加注解@RequestBody @Valid(源自javax.validation.Valid);

2.对象实体类中添加相关校验注解(源于javax.validation.constraints).

2.示例说明

1.请求类

package com.kawaxiaoyu.api.appointCourse.controller;


import com.kawaxiaoyu.api.appointCourse.dto.ApplyInfoDto;
import com.kawaxiaoyu.api.appointCourse.service.impl.AppointCourseServiceImp;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: AppointCourseController
 * @Desc: 约课内容管理
 * @Author: txm
 * @Date: 2021/8/31 18:09
 **/
@RequestMapping("/appoint")
@RestController
public class AppointCourseController {

    @Autowired
    private AppointCourseServiceImp appointCourseService;


    /**
     * @Author: txm
     * @Description: 添加线上课程申请
     * @Param: [applyInfoDto]
     * @return: io.geekidea.springbootplus.framework.common.api.ApiResult
     * @Date:  2021/9/1 15:49
     **/
    @PostMapping("/addApplyCourseInfo")
    public ApiResult addApplyCourseInfo(@RequestBody @Valid ApplyInfoDto applyInfoDto){
        appointCourseService.addApplyCourseInfo(applyInfoDto);
        return ApiResult.ok();
    }
}

2.请求对象实体类

package com.kawaxiaoyu.api.appointCourse.dto;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
 * @ClassName: ApplyInfoDto
 * @Desc: 线上课程申请参数
 * @Author: txm
 * @Date: 2021/9/1 15:38
 **/
public class ApplyInfoDto {
    @NotEmpty(message = "联系人不允许为空")
    private String name;
    @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(16([2-3]|[5-7]))|(17[013678])|(18[0,5-9])|(19[0-9]))\\d{8}$",message = "手机号格式不正确:支持11位手机号")
    private String mobile;
    @Min(value = 1,message = "字典项不允许为空")
    private int dictId;
    @Min(value = 1,message = "瑜伽馆id不允许为空")
    private int studioId;
    // 省略get/set方法
}

3.测试说明

2.2 参数为单个参数校验方式

场景说明:接口中只对一个参数进行校验

controller类上添加@Validated,标识整个类可以进行注解校验;方法参数上添加校验注解.

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Min;


@RequestMapping("/test")
@RestController
@Validated
public class TestController {

    @GetMapping("/course")
    public String test( @Min(value = 1,message = "不允许为0") Integer id){
        // 业务代码省略
    }
}

2.3 分组校验

  场景说明,查询传递参数对象中CourseTableDto为两个接口的公共参数,其中一个接口对type字段有大小限制要求,另一个接口对此字段没有限制.

CourseTableDto对象参数:

public class CourseTableDto {
    @Min(value = 1,message = "瑜伽馆id不允许为空")
    private int studioId;
    @Range(min = 1,max = 2,message = "课程类型:1.团课,2小班课")
    private int type;
    @Pattern(regexp = "((20)[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])",message = "时间格式支持:yyyy-MM-dd")
    private String dateTime;
    
// 省略get/set
}

实现方式:@validated注解分组完成.

1.自定义两个接口,分别标识校验是否校验type字段

/**
 * @ClassName: CourseTableDtoNoType
 * @Desc: CourseTableDto中不进行校验type
 * @Author: txm
 * @Date: 2021/9/7 11:35
 **/
public interface CourseTableDtoNoType {
}



/**
 * @ClassName: CourseTableDtoType
 * @Desc: CourseTableDto中分组校验type类型
 * @Author: txm
 * @Date: 2021/9/7 11:34
 **/
public interface CourseTableDtoType {
}

2.CourseTableDto中带有校验注解字段中添加分组属性,表明校验类型为自定分组,非默认分组

带有校验注解的字段的注解中添加group属性:groups = CourseTableDtoType.class.

public class CourseTableDto {
    @Min(value = 1,message = "瑜伽馆id不允许为空")
    private int studioId;
    @Range(min = 1,max = 2,message = "课程类型:1.团课,2小班课",groups = CourseTableDtoType.class)
    private int type;
    @Pattern(regexp = "((20)[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])",message = "时间格式支持:yyyy-MM-dd")
    private String dateTime;
    
// 省略get/set
}

3.controller请求接口中对type进行校验的接口中添加校验的分组接口类型

@Validated中添加分组标记接口:@Validated(value = {CourseTableDtoType.class,Default.class})

@PostMapping("/findPersonalTeacherListByDay")
    public ApiResult findStudioCourseTablesByDay(@RequestBody @Validated(value = {CourseTableDtoType.class,Default.class}) CourseTableDto courseTableDto){
      // 业务实现已忽略
    }

 注意:其他字段默认分组是Default.class(javax.validation.groups.Default),如果不添加的话其他字段会上的校验注解不会生效.具体原因说明可以参考:springboot中注解校验@Valid@Validated失效场景汇总(持续更新)_卖柴火的小伙子的博客-CSDN博客

4.测试两个接口是否只有一个校验type效果

不校验type字段的显示正常(type可以不传递或是数据值任意)

校验type的接口参数校验异常

补充:如果出现注解不生效的情况,可以参考下面源码分析进行定位;这里汇总常见不生效的处理方案:

参数或是对象上使用的注解最好是使用hibernate原生注解,注解名相同但引入的包不同会存在此种场景.

更多失效场景参考:springboot中注解校验@Valid@Validated失效场景汇总(持续更新)_卖柴火的小伙子的博客-CSDN博客

2.4 级联校验(校验对象中属性为对象的场景)

场景说明:更新教师基本信息,教师基本信息中会包含每周的工作时间设置信息.

对employInfoDto中属性personalWeekDayList中的personalWeekDayDto中的各项属性进行校验,需要做的操作是从主实体类的personalWeekDayList属性上添加@Valid注解!

1.教师信息实体类

public class EmployInfoDto implements Serializable {
    private static final long serialVersionUID = 6364772811350252018L;

    // 省略部分属性信息

    @Valid
    private List<PersonalWeekDayDto> PersonalWeekDayList;
        // 省略get/set方法

}

2.教师周信息实体类

public class PersonalWeekDayDto {

    @Range(min = 1,max = 7,message = "每周从1到7")
    private int weekDay;

    @Range(min = 1,max = 2,message = "上班状态:1.上班;2.不上班")
    private int state;

    @Pattern(regexp = "(0[0-9]|1[0-9]|2[0-4]):([0-5][0-9])",message = "时间格式正确:HH:mm")
    private String startTime;

    @Pattern(regexp = "(0[0-9]|1[0-9]|2[0-4]):([0-5][0-9])",message = "时间格式正确:HH:mm")
    private String endTime;

    // 省略get/set
}

2.5 自定义注解

场景要求:多条件查询,开始时间与结束时间为空时不校验,不为空时校验是否是yyyy-MM-dd格式.

实现步骤:

1.自定义校验时间格式校验注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {PersonalPatternValidator.class })   // 约定自定义的校验注解校验逻辑的实现类
public @interface PersonalPattern {

    // 约束异常模板消息
    String message() ;

    // 约束指定可指定的分组
    Class<?>[] groups() default { };

    // 指定与约束声明相关联的有效负载
    Class<? extends Payload>[] payload() default { };

}

2.创建自定义注解业务的校验业务类

需要实现ConstraintValidator接口,泛型为自定义注解以及校验的参数类型

public class PersonalPatternValidator  implements ConstraintValidator<PersonalPattern, String> {


    @Override
    public void initialize(PersonalPattern parameters) {
    }


    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        // 校验是否为空或是是否为null
        if ( value == null || "".equals(value)) {
            return true;
        }

        // 校验格式是否符合
        boolean flag= true;
        Pattern pattern = Pattern.compile("((20)[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
        Matcher matcher = pattern.matcher(value);
        if(!matcher.matches()){
            flag=false;
        }
        return flag;
    }
}

3.实体类添加自定义注解

public class ShareUserDto {

    private long userId;
    private String firstName;
    private String mobile;
    private int dayTime;  

    @PersonalPattern(message = "开始时间格式支持:yyyy-MM-dd")
    private String startTime;
    @PersonalPattern(message = "结束时间格式支持:yyyy-MM-dd")
    private String endTime;

    private String login;
    // 省略get/set
}

4.测试

传递参数为空时或不传递字段时,接口正常(业务数据问题,查询数据为空)

传递参数不为空,格式不正确时校验异常信息返回

2.5.1 自定义注解进行关联校验

场景说明

     添加用户信息,现有角色信息(1.管理员;2.店长;3.前台;4.教练),教师类型(1.团课,2.私教);用户只有是教练角色才会教师类型,其他角色类型中教师类型可以为空.传递参数对象中,参数校验存在关联关系!此种场景处理方案是直接以请求对象为单位进行校验,然后根据对象中的属性进行关联校验!

实现步骤:

1.自定义注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {TeacherTypeValidator.class })
public @interface TeacherTypeValid {

    // 约束异常模板消息
    String message() ;

    // 约束指定可指定的分组
    Class<?>[] groups() default { };

    // 指定与约束声明相关联的有效负载
    Class<? extends Payload>[] payload() default { };

}

2.自定义注解处理实现类

public class TeacherTypeValidator implements ConstraintValidator<TeacherTypeValid, EmployInfoDto> {
    @Override
    public void initialize(TeacherTypeValid constraintAnnotation) {

    }

    @Override
    public boolean isValid(EmployInfoDto employInfoDto, ConstraintValidatorContext context) {
        int a=0;
        if(employInfoDto == null || CollectionUtil.isEmpty(employInfoDto.getRoleIdList())){
            return true;
        }
        // 角色类型为4:教练时才允许校验教师类型字段
        if(employInfoDto.getRoleIdList().contains(4)){
            if(employInfoDto.getTeacherTypeList().isEmpty()){
                return false;
            }
            for (Integer teacherType : employInfoDto.getTeacherTypeList()) {
                if(teacherType < 1 || teacherType > 2){
                    return false;
                }
            }
        }
        return true;
    }
}

3.请求对象中添加注解

@TeacherTypeValid(message = "教师类型不合法:1.团课老师;2.私教课老师")
public class EmployInfoDto implements Serializable {
    private static final long serialVersionUID = 6364772811350252018L;

    // 教师类型集合   
    private List<Integer> teacherTypeList;

    @Size(min = 1,max = 5,message = "角色类型不合法:1.管理员;2.店长;3.前台;4.教练")
    @NotNull(message = "角色id不能为空")
    private List<Integer> roleIdList;

    // 省略get/set方法

}

2.5.2 同一个自定义注解对多个对象进行相同关联校验

场景说明:

        课程预约设置支持两种设置规则,创建课程时可以添加课程级别的预约设置信息,也可以遵循系统公共的预约设置。系统和课程设置中的课程开始时间都支持两种预约方式,一种是提前指定天数,并指定选择日期的时间段(24小时之内),另一种是仅支持开课前指定分钟内进行预约。现需要对开课前分钟数进行校验,如果选择第一种方式则开课前分钟数允许为空,选择第二种方式则不允许为空。

分析:根据预约类型校验开课前分钟数实现方式可以参考上个案例,这个场景难点在两种设置方式中都需要根据类型进行校验开始时间分钟数是否为空.通过上面的自定义案例知道自定义注解实现类继承接口ConstraintValidator中的泛型仅支持一个对象.但是对于多个对象校验相同内容没必要重复去定义注解.可以采用抽象继承的方式实现,ConstraintValidator中泛型指定为抽象父类,并将获取预约类型以及开课前分钟数的方法进行抽象化,以便于注解实现处理中能取到对应的值.这样就能在两个不同的对象实现同样的校验业务处理.

实现步骤:

1.自定义注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {StartTimeValidator.class })
public @interface StartTimeValid {

    // 约束异常模板消息
    String message() ;

    // 约束指定可指定的分组
    Class<?>[] groups() default { };

    // 指定与约束声明相关联的有效负载
    Class<? extends Payload>[] payload() default { };
}

2.自定义注解处理实现类

public class StartTimeValidator implements ConstraintValidator<StartTimeValid,CommonWork> {
    @Override
    public void initialize(StartTimeValid constraintAnnotation) {

    }

    @Override
    public boolean isValid(CommonWork value, ConstraintValidatorContext context) {
        int a=0;
        if(value.getType() ==1){
            if(StringUtils.isEmpty(value.getStartTime())){
                return false;
            }
        }
        return false;
    }
}

3.请求对象中添加注解

公共预约设置抽象类:

public abstract class CommonWork {

    // 获取开课前分钟数
    abstract String getStartTime();
    
    // 获取预约方式类型(1.提前指定分钟数;2提前指定天数,指定开始结束时间段)
    abstract int getType();
}

系统级别预约设置信息伪代码:

@StartTimeValid(message = "开始时间不允许为空")
public class SystemSetting extends CommonWork {

    // 系统预约设置中第二种预约方式,开课前分钟数
    private String startTime;

    private int type;

    private int age;

    // 省略get/set

}

课程级别预约设置信息:

@StartTimeValid(message = "开始时间不允许为空")
public class CourseSetting extends CommonWork {

    // 课程设置中的开课前分钟数
    private String startTime;

    private String name;
    private int type;

    // 省略get/set

}

请求以及测试信息:

@RequestMapping("/valid")
@RestController
public class ValidController {

    @PostMapping("/doCourseSetting ")
    public String doCourseSetting (@RequestBody @Validated CourseSetting courseSetting ){
        System.out.println(workOne);
        return "success";
    }

    @PostMapping("/doSystemSetting ")
    public String doSystemSetting (@RequestBody @Validated SystemSetting systemSetting ){
        System.out.println(worktwo);
        return "success";
    }
}

3.校验源码

参考:springboot中@Vlidated注解校验源码_卖柴火的小伙子的博客-CSDN博客;

官方注解约束文档:Jakarta Bean Validation specification

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卖柴火的小伙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值