百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

springboot中注解校验@Valid@Validated使用详细说明

lipiwang 2024-10-22 15:53 9 浏览 0 评论

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}#34;,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),如果不添加的话其他字段会上的校验注解不会生效.


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


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



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



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


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



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";
    }
}





—— END ——

作者| 卖柴火的小男孩啊

多年后端开发经验,坚持分享更多java干货内容!

如果你喜欢我的文章,不妨点赞、转发、收藏一下哦!

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: