Hibernate validator注解及Spring Boot自定义Hibernate Validator注解校验(超级详细)

news2024/11/15 4:51:15

一 Hibernate validator是什么

        验证数据是贯穿整个应用层(从表示层到持久层)的常见任务。通常在每一层中都需要实现相同的验证逻辑,这样既耗时又容易出错。为了避免这些验证的重复,开发认原经常将验证逻辑直接捆绑到Model域中,将域类与验证代码(实际上是关于类本身的元数据)混在一起。

        Jakarta Bean Validation 3.0定义了用于实体和方法验证的元数据模型和API。默认的元数据源是注释,能够通过使用XML覆盖和扩展元数据。API没有绑定到特定的应用程序层或编程模型。它特别不局限于web层或持久性层,并且可用于服务器端应用程序编程以及大量的客户端Swing应用程序开发人员。

        Hibernate Validator是Jakarta Bean Validation的实现。解决了业务代码中多次出现if校验使得代码臃肿的问题,可以让业务代码和小样逻辑分开,不在编写重复的校验逻辑。

二 常用的内置注解

        下面是Jakarta Bean Validation API中指定的所有校验的列表。所有这些校验都应用于字段/属性级别,Jakarta Bean验证规范中没有定义类级别的校验。如果您正在使用Hibernate对象-关系映射器,那么在为模型创建DDL时需要考虑一些校验(请参阅“Hibernate元数据的影响”)。

注解数据类型说明
@AssertFalseBoolean, boolean验证注解的元素值是false
@AssertTrueBoolean, boolean验证注解的元素值是true
@DecimalMax(value=x)BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。验证注解的元素值小于等于@ DecimalMax指定的value值
@DecimalMin(value=x)BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。验证注解的元素值小于等于@ DecimalMin指定的value值
@Digits(integer=整数位数, fraction=小数位数)BigDecimal,BigInteger,String,byte,short,int,long和原始类型的相应包装。HV额外支持:Number和CharSequence的任何子类型。验证注解的元素值的整数位数和小数位数上限
@Futurejava.util.Date,java.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate; Additionally supported by HV, if the Joda Time date/time API is on the classpath: any implementations of ReadablePartial and ReadableInstant验证注解的元素值(日期类型)比当前时间晚
@Max(value=x)BigDecimal,BigInteger,byte,short,int,long和原始类型的相应包装。HV额外支持:CharSequence的任何子类型(评估字符序列表示的数字值),Number的任何子类型。验证注解的元素值小于等于@Max指定的value值
@Min(value=x)BigDecimal,BigInteger,byte,short,int,long和原始类型的相应包装。HV额外支持:CharSequence的任何子类型(评估char序列表示的数值),Number的任何子类型。验证注解的元素值大于等于@Min指定的value值
@NotNull所有类型验证注解的元素值不是null
@Null 所有类型验证注解的元素值是null
@Pastjava.util.Date,java.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate; ,则由HV附加支持:ReadablePartial和ReadableInstant的任何实现。验证注解的元素值(日期类型)比当前时间早
@Pattern(regex=正则表达式, flag=)CharSequence验证注解的元素值与指定的正则表达式匹配
@Size(min=最小值, max=最大值)字符串,集合,映射和数组。HV额外支持:CharSequence的任何子类型。验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Valid 
Any non-primitive type(引用类型)
验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象
@NotEmptyCharSequence,Collection, Map and Arrays验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Range(min=最小值, max=最大值)CharSequence, Collection, Map and Arrays, BigDecimal, BigInteger, CharSequece, byte, short, int, long以及原始类型各自的包装验证注解的元素值在最小值和最大值之间
@NotBlankCharSequence验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Length(min=下限, max=上限)CharSequence验证注解的元素值长度在min和max区间内
@EmailCharSequence验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

三,自定义Hibernate Validator校验注解

        自定义Hibernate Validator校验注解,首先它是注解,所以我们先来看看Spring Boot如何自定义注解。

3.1 Spring Boot自定义注解

3.1.1 元注解

        自定义注解,需要用到元注解,元注解就是注解注解的注解。常用的元注解包括:

  • Target:描述了注解的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
    • METHOD:用于描述方法
    • PACKAGE:用于描述包
    • PARAMETER:用于描述方法变量
    • TYPE:用于描述类、接口或enum类型
    • FIELD:用来描述字段
  • Retention:表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:
    • SOURCE:在源文件中有效,编译过程中会被忽略
    • CLASS:随源文件一起编译在class文件中,运行时忽略
    • RUNTIME:在运行时有效,可以通过反射获取到
  • Documented:是java在生成文档时,是否显示注解的开关。

只有定义为RetentionPolicy.RUNTIME时,我们才能通过注释反射获取到注释。

3.1.2 反射获取注解

        可以通过反射来获取注解。假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用,可以定义如下:

  • 定义注解:
@Target(ElementType.FIELD)  //注解用于字段上
@Retention(RetentionPolicy.RUNTIME) //保留到运行时,可通过注解获取
public @interface MyField{
    String description();
    int length();
}
  • 反射获取注解
public class MyFieldTest{

    //使用我们的自定义注解
    @MyField(description = "用户名", length = 12)
    private String username;

    @Test
    public void testMyField(){
        
        //获取类模板
        Class c = MyFieldTest.class;

        //获取所有字段
        for(Field f : c.getDeclaredFields()){
            //判断这个字段是否有MyField注解
            if(f.isAnnotationPresent(MyField.class)){
                MyFielld annotation = f.getAnnotation(MyFiled.class);
                System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
            }
        }

    }
}

3.1.3 AOP使用自定义注解

        自定义注解一般都是配合AOP拦截器使用,因此使用步骤为

  • 定义注解
package com.****.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

}
  • 实现切面类,用@Aspect来实现。定义切面并实现相关功能
package com.****(项目名称).demo.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAnnotationImpl {

    @Pointcut("@annotation(com.****.demo.annotation.MyAnnotation)")
    public void myPointcut(){}

    @Around(value = "myPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws  Throwable {
        System.out.println("......this is before around.....");
        Object ret = joinPoint.proceed();
        System.out.println("......this is after around.....");
        return ret;
    }

    @Before(value = "myPointcut()")
    public void before() throws Throwable {
        System.out.println("******this is before function******");
    }

    @After(value = "myPointcut()")
    public void after() throws  Throwable {
        System.out.println("******this is after function******");
    }
}
  • Controller查看效果
package com.****.demo.controller;

import com.****.demo.annotation.MyAnnotation;
import com.****.demo.common.CallResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/Annotation")
public class AnnotationController {

    @PostMapping("/AnnotationAop")
    @MyAnnotation
    public CallResult AnnotationAop(){
        System.out.println("注解测试成功");
        return CallResult.ok();
    }
}
  • 运行结果:

 注解切面已加载成功。

3.2 自定义Hibernate Validator注解校验

        Jakarta Bean Validation API定义了如@NotNull, @Size等一整套标准校验注解。如果这些内置校验还不够,您可以根据特定的验证需求轻松创建定制校验。

自定义注解校验需要义下步骤:

  1. 创建一个校验注解(creating a constraint annotation)
  2. 实现一个校验器(Implement a validator)
  3. 定义一个默认的错误信息(Define a default error message)

本例定义一个自定义校验注释,来判断所注释的字符串是否全为大写或全为小写。

3.2.1 步骤一,首先需要的是一种表达两种case模式的方法。虽然你可以使用String常量,但更好的方法是使用枚举:

package com.****.demo.common;

public enum CaseMode {
    UPPER,
    LOWER;
}

3.2.2 步骤二,定义校验注解

package com.****.demo.annotation;

import com.****.demo.common.CaseMode;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(List.class)
public @interface CheckCase {
    String message() default "这里是默认错误消息,默认的!默认的!默认的!";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    CaseMode value();

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
    CheckCase[] value();
    }
}


        注释类型使用@interface关键字定义。注释类型的所有属性都以类似方法(method-like manner)的方式声明。Jakarta Bean Validation API的规范要求所有校验注解定义:

  • 一个默认的message属性,用于在校验被违反时返回错误消息
  • 一个groups属性,允许定义分组校验,它必须默认为Class<?>类型的空数组。
  • 一个payload属性,用户可以将用户自定义的对象分配给校验。这个属性不是API自己用的。一个用户自定义的payload例子如下,定义一个Severity类:
public class Severity {
    public interface Info extends Payload {
    }

    public interface Error extends Payload {
    }
}
public class ContactDetails {
    @NotNull(message = "Name is mandatory", payload = Severity.Error.class)
    private String name;

    @NotNull(message = "Phone number not specified, but not mandatory",
            payload = Severity.Info.class)
    private String phoneNumber;

    // ...
}

        现在,客户端可以在经过ContactDetails的实例验证之后,通过ConstraintViolation.getConstraintDescriptor().getPayload()进入一个校验的severity,并通过severity来调整其行为。

        除了这三个强制属性外,还有一个value属性,允许指定所需的case模式。value值是一个特殊的值,如果它是唯一指定的属性,当使用注解时可以省略,例如:@CheckCase(CaseMode.UPPER)。

        此外,校验注解还使用了一些元注解:

  • @Target:用于指定注解的作用范围,包括类、方法、字段等
  • @Retention:指定改元注解的保留策略,包括source,class和runtime
  • Constraint(validatedBy = CheckCaseValidator.class):将注释类型标记为校验注释,并指定用于验证用@CheckCase注解的元素的验证器。如果校验可以用于多个数据类型,则可以指定多个验证器,每个验证器对应一个数据类型。通过校验器返回的结果(true/false)来判断是否抛出异常信息。
  • @Repeatable(List.class):指示注释可以在同一位置重复多次,通常使用不同的配置。List是包含注解类型(containing annotation type)。

        示例中也显示了名为List的包含注释类型。它允许在同一个元素上指定多个@CheckCase注释,例如使用不同的验证组和消息。虽然可以使用其他名称,但Jakarta Bean Validation规范建议使用名称List,并使注释成为相应校验类型的内部注释。

3.2.3 校验器

        定义注解之后,需要创建一个实现ConstraintValidator接口的校验器用来校验带有@CheckCase注解的元素:

package com.****.demo.common;

import com.****.demo.annotation.CheckCase;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }

    // value:待检验的字符串
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ( value == null ) {
            return true;
        }

        if ( caseMode == CaseMode.UPPER ){
            return value.equals( value.toUpperCase() );
        } else {
            return value.equals( value.toLowerCase() );
        }
    }
}

 试一下:

在User.demoName上加上注释@CheckCase(CaseMode.LOWER)

@Data
@Component
@ConfigurationProperties(prefix = "user")
public class User implements Serializable {

    @ApiModelProperty(value = "名称")
    @NotBlank(message = "name不能为空(哈哈,自定义哒)")
    @CheckCase(CaseMode.LOWER)
    private String demoName;

    @ApiModelProperty(value = "年龄")
    @NotNull(message = "age 不能为空")
    private Integer age;

    @ApiModelProperty(value = "id")
    @NotNull(message = "id 不能为空")
    private Integer id;
}

   demoName大写输入"UPPER”

错误显示:

          ConstraintValidator接口定义了输入两个参数类型。第一个定义了需要校验的注解的类型(这里是ChecCase),第二个定义了是校验器能处理的参数类型(这里是String)。如果一个校验器可以支持多种数据类型,则每种数据类型都需要一个ConstraintValidator并像上面的方法进行实现和注册。

        isValid()方法则包含了实际的校验逻辑。@CheckCase是用来校验相应的字符串是否为全大写或全小写的字符串,根据initialize()中定义的caseMode来判断。注意,Jakarata Bean Validation 建议null是有效的(即返回true)。如果null不是有效数据,则需要加上注解@NotNull。

        ConstraintValidatorContext: 上例CheckCaseValidator校验器的实现中,isValid()方法只返回了true和false,使用了默认的error message。可以通过传入的ConstraintValidatorContext对象来增加客户自定义的error message或完全禁用默认错误消息,只定义自定义错误消息,如下例:

public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }


    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
        if ( value == null ) {
            return true;
        }

        boolean isValid;

        if ( caseMode == CaseMode.UPPER ){
            isValid = value.equals( value.toUpperCase() );
        } else {
            isValid = value.equals( value.toLowerCase() );
        }

        if ( !isValid ) {
            constraintContext.disableDefaultConstraintViolation();
            constraintContext.buildConstraintViolationWithTemplate(
                    "这里是我新定义的错误消息!!!!"
            )
                    .addConstraintViolation();
        }
        return isValid;
    }
}

试一下,输入给成大写的“UPPER”

错误显示:

Nice~完美!

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.9 基础综合案例|pyechart第三方包

文章目录json数据格式pyecharts模块介绍pyecharts快速入门数据处理这里使用比较经典的pyechart的第三方包。json数据格式 json是一种轻量级的数据交互形式。可以按照json指定的格式去组织和封装数据。或者这么说本质上json就是一个带有特定格式的字符串。 主要功能&#xff1…

【金猿案例展】正官庄——全渠道会员数据治理驱动商业增长

‍珍岛集团案例本项目案例由珍岛集团投递并参与“数据猿年度金猿策划活动——《2022大数据产业年度创新服务企业》榜单/奖项”评选。‍数据智能产业创新服务媒体——聚焦数智 改变商业随着商业品牌的全渠道裂变式发展&#xff0c;对DTC直营会员为中心的综合数据运营提出了新的…

【自学Java】Java基本数据类型

Java基本数据类型 Java基本数据类型 Java 基本数据类型如下表&#xff1a; 序号数据类型大小/位可表示的数据范围默认值1long&#xff08;长整数&#xff09;64-9223372036854775808&#xff5e;92233720368547758070L2int&#xff08;整数&#xff09;32-2147483648&#x…

设置 MYSQL 数据库编码为 utf8mb4

utf-8编码可能2个字节、3个字节、4个字节的字符&#xff0c;但是MySQL的utf8编码只支持3字节的数据&#xff0c;而移动端的表情数据是4个字节的字符。如果直接往采用utf-8编码的数据库中插入表情数据&#xff0c;java程序中将报SQL异常&#xff1a; java.sql.SQLException: Inc…

带音频播放的MPlayer播放器在ARM上的移植笔记

前言 mplayer想要播放带音频的视频文件&#xff0c;需要依赖alsa-lib和zlib&#xff0c;所以交叉编译mplayer前还需要先编译alsa-lib和zlib 一、alsa-lib alsa-lib 是 ALSA 提供的一套 Linux 下的 C 语言函数库&#xff0c;需要将 alsa-lib 移植到板卡上&#xff0c;这样基于…

人话解读LGPLv3

大家都知道&#xff1a;你调用了 LGPL的库&#xff0c;你还是可以开发一个闭源程序。这就说明&#xff0c;LGPL比GPL要宽松。但并不像想象的那么简单。一、为什么会有LGPL作为GPL的发明人Stallman&#xff0c;是自由软件的死忠坚定维护者&#xff0c;为什么还允许让别人用了自己…

2022年终总结-2023新年快乐

这是学习笔记的第 2446篇文章新的一年了&#xff0c;还是得总结点东西&#xff0c;本来想盘一下自己买了多少书&#xff0c;做了哪些有意义的事情&#xff0c;想想我还是自己先慢慢盘吧&#xff0c;发不发出来另说&#xff0c;还是希望写点自己的感悟&#xff0c;也希望对大家有…

LabVIEW共享变量

LabVIEW共享变量 创建共享变量 要创建共享变量&#xff0c;必须先打开一个LabVIEW项目。在项目浏览器窗口中&#xff0c;右键单击终端、项目库或项目库中的文件夹&#xff0c;从快捷菜单中选择新建(New) 变量(Variable)&#xff0c;打开共享变量属性(Shared Variable Proper…

dubbo源码实践-SPI扩展

1 概述 SPI的官方文档说明&#xff1a;Dubbo SPI | Apache Dubbo SPI 全称为 Service Provider Interface&#xff0c;是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中&#xff0c;并由服务加载器读取配置文件&#xff0c;加载实现类。这样可以在运行时&…

我的2022总结

博客记录 踏石留印 抓铁有痕 使用csdn写博客&#xff0c;发帖子&#xff0c;帮助网友回答问题。都是实实在在满足了学习&#xff0c;交流的需求 这是我自己使用 CSDN 各种功能记录&#xff1a; 工作方面&#xff1a; 年初参与了公司的一个产品&#xff0c;主要负责串口服务…

Allegro上如何计算阻抗操作指导

Allegro上如何计算阻抗操作指导 Allegro上同样可以快捷的进行阻抗计算,免去了用第三方软件计算的麻烦,以下图为例 具体操作如下 选择X-section在层叠中把每个层的Dielectric Constant填写正确,即板材的Er值

01月份图形化二级打卡试题

活动时间 从2023年 1月1日至1月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; &#xff08;1&#xff09;小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 &#xff08;2&#xff09;小朋友做完题目后&…

阿里云迎来新主帅,张勇将交出怎样的答卷?

‍‍数据智能产业创新服务媒体——聚焦数智 改变商业岁末年尾&#xff0c;回顾过去展望新篇之际&#xff0c;阿里巴巴集团于29日通过两封邮件宣布了组织架构的调整。以“沉稳内敛”著称的张勇&#xff0c;在解决公司问题时&#xff0c;却尽显雷霆手段。敢于作出不完美的决定在…

【算法】静态单链表、双链表、单调栈与单调队列

文章目录1.单链表2.双链表3.单调栈4.单调队列1.单链表 考虑到效率问题&#xff0c;如果每次都去new结点效率比较慢&#xff0c;平时做题时不采用动态:在有严格的时间要求的环境中&#xff0c;不能频繁使用new操作,new的底层涉及内存分配&#xff0c;调用构造函数&#xff0c;指…

2023创业可以做什么项目,适合新手的六个创业项目推荐

大家好&#xff0c;我是蝶衣王的小编 ​2022年已经进入最后一天了&#xff0c;明天就要步入2023年&#xff0c;个人感觉&#xff0c;明年注定是不平凡的一年&#xff0c;疫情解封&#xff0c;经济生产逐渐恢复&#xff0c;明年开始&#xff0c;创业或者做副业的人肯定会越来越…

视频分割很简单,教你方法三分钟搞定视频剪辑

很多朋友不知道怎么分割视频&#xff0c;今天小编就分享怎么在电脑上分割视频的方法&#xff0c;使用媒体梦工厂操作起来不难&#xff0c;新手小白也能轻松学会&#xff0c;一起接着往下看吧。 第一步&#xff0c;开始剪辑之前&#xff0c;小编准备了多段视频用于演示分割效果&…

【金猿案例展】某大型国有银行——智慧金融产业大脑建设

‍拓尔思案例本项目案例由拓尔思投递并参与“数据猿年度金猿策划活动——《2022大数据产业年度创新服务企业》榜单/奖项”评选。‍数据智能产业创新服务媒体——聚焦数智 改变商业该银行为提高金融领域产业经济分析能力&#xff0c;建设智慧金融产业大脑&#xff0c;通过投融资…

计算机组成原理【1】

目录 考点1&#xff1a;硬件发展———————————————————————————— 一.计算机硬件的基本组成 1.早期冯诺依曼机 &#xff08;1&#xff09;冯.诺依曼计算机的特点: 2.现代计算机的结构 3.总结图 二.各个硬件的工作原理 1.寄存器MAR,MDR 2.主存…

Redis 如何解决内存占用过大、不释放的问题

错误日志 通过 redis.log 可以看到错误日志如下&#xff1a;Cannot allocate memory 15602:M 30 Dec 2022 17:39:09.988 * RDB memory usage when created 19775.56 Mb 15602:M 30 Dec 2022 17:39:44.766 # Done loading RDB, keys loaded: 529954, keys expired: 26. 15602:…

基于长短期记忆网络和凸优化算法的综合智能电网的可再生能源预测(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…