spring 中 @Validated/@Valid 注解的使用

一、背景

日常开发过程中,经常遇到大量的参数进行校验, 在业务中还要抛出异常等校验信息, 在代码中相当冗长, 充满了 if-else 这种校验代码, 代码不够优雅,使用 spring 的 javax.validation 注解式参数校验,可以免去繁琐的校验。

二、@Validated 注解

单层对象的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {

    @NotNull(message = "id添加时可以为空,更新时不能为空", groups = {UpdateGroup.class})
    private Integer id;

    @NotBlank(message = "名字不能为空", groups = {UpdateGroup.class, AddGroup.class})
    @Size(min = 6, max = 12, message = "名字的长度在6到12之间", groups = {UpdateGroup.class, AddGroup.class})
    private String username;
    @NotNull(message = "年龄不能为空", groups = {UpdateGroup.class, AddGroup.class})
    @Min(value = 20, message = "最小年龄要大于20", groups = {UpdateGroup.class, AddGroup.class})
    private Integer age;

    /**
     * 没有指定组别,默认是Default.class组别
     */
    @NotBlank(message = "编号不能为空")
    @Pattern(regexp = "^(广告组)\\d$", message = "编号要以广告组开头,以1-9结尾")
    private String number;
    /**
     * @see com.tt.enums.PersonStatusEnum
     */
    @NotNull(message = "添加人员的时候状态不能为空", groups = {AddGroup.class})
    private Integer status;
    @NotBlank(message = "添加人员的时候如果状态是类别2的时候角色字段不能为空", groups = {SecondGroup.class})
    private String role;
}

1. 没有采用注解,需要自己写逻辑判断

@PostMapping("no")
public String no(Person person) {
    if (person.getUsername() == null) {
        throw new RuntimeException("姓名不能为空");
    }
    if (person.getUsername().length() < 6 || person.getUsername().length() > 12) {
        return "姓名长度必须在6 - 12之间";
    }
    if (person.getAge() == null) {
        return "年龄不能为null";
    }
    if (person.getAge() < 20) {
        return "年龄最小需要20";
    }
    return "不用注解成功!";
}

2. 常用的校验注解

  1. @NotNull

    被注释的元素必须不为 null

  2. @Min(value)

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

  3. @Max(value)

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

  4. @Size(max, min)

    被注释的元素的大小必须在指定的范围内,用在集合大小或者字符串的大小

  5. @Pattern(value)

    被注释的元素必须符合指定的正则表达式

  6. @NotEmpty

    被注释的元素必须不为 null 或者不为空,可校验字符、集合、Map

  7. @NotBlank

    被注释的元素必须不为空字符串

  8. @Range(min ,max)

    数值类型的范围大小,相当于 @Min+@Max 两个注解

3. 采用 Validated 注解校验

3.1 采用 @Valid 注解校验 (@Valid 不提供分组的功能)

3.1.1 接口层

/**
 * 采用@valid注解校验 只校验没有指定分组的字段,也相当于校验Default的组别
 *
 * @param person
 * @return
 */
@PostMapping("valid")
public String valid(@Valid @RequestBody Person person) {
    return "valid注解校验成功!";
}

3.2 @Validated 不分组校验(单层对象)

3.2.1 接口层

/**
 * 不指定分组,只校验默认的组别Default
 *
 * @param person
 * @return
 */
@PostMapping("default")
public String defaultGroup(@Validated() @RequestBody Person person) {
    return "Validated注解默认成功!";
}

3.3 @Validated 分组校验(单层对象)

由于在项目中存在几个接口共用一个实体类,在不同的接口中某些字段的规则是不一样的,如果想要用同一个对象的话,这个时候就需要用到分组的功能了,指定字段在某个组别下的规则。

3.3.1 接口层

/**
 * 指定组别校验,校验自定义的AddGroup组别
 *
 * @param person
 * @return
 */
@PostMapping("add")
public String addGroup(@Validated({AddGroup.class}) @RequestBody Person person) {
    return "Validated注解添加成功!";
}

/**
 * 指定组别校验,校验自定义的UpdateGroup组别和默认的组别Default
 *
 * @param person
 * @return
 */
@PostMapping("update")
public String updateGroup(@Validated({UpdateGroup.class, Default.class}) @RequestBody Person person) {
    return "Validated注解更新成功!";
}

3.4 @Validated 根据前端的传参状态,添加不同的组别,最后进行手动校验(单层对象)

3.4.1 接口层

/**
 * 框架先校验AddGroup组别的参数,然后如果状态是类别2,则需要手动添加要
 校验SecondGroup和Default组别,最后再调用校验的工具类
 *
 * @param person
 * @return
 */
@PostMapping("manual")
public String manualGroup(@Validated({AddGroup.class}) @RequestBody Person person) throws Exception {
    List<Class> groupClasses = new ArrayList<>();
    if (PersonStatusEnum.isSecond(person.getStatus())) {
        groupClasses.add(SecondGroup.class);
        groupClasses.add(Default.class);
    }
    //转成数组
    Class[] groups = groupClasses.toArray(new Class[0]);
    //手动调用校验的方法
    ValidatorUtils.validateEntity(person, groups);
    return "Validated注解手动添加成功!";
}

3.4.2 工具类

@Slf4j
public class ValidatorUtils {
    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    /**
     * 手动校验
     *
     * @param object
     * @param groups
     * @throws Exception
     */
    public static void validateEntity(Object object, Class<?>... groups) throws Exception {
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            StringBuilder errorMsg = new StringBuilder();
            for (ConstraintViolation<?> e : constraintViolations) {
                errorMsg.append(e.getMessage() + ";");
            }
            throw new Exception(errorMsg.substring(0, errorMsg.toString().length() - 1));
        }
    }
}

3.5 @Validated 嵌套对象校验 (级联)

项目中经常会遇到一个对象中嵌套另外一个对象的情况(一对多的关系),这个时候就要用到级联校验

3.5.1 接口层
/**
 * 部门添加人员的校验,指定组别校验,部门的对象包含了一个人员对象的集合
 *
 * @param dept
 * @return
 */
@PostMapping("add")
public String addGroup(@Validated({AddGroup.class, Default.class}) @RequestBody Dept dept) {
    return "添加部门人员成功!";
}

3.5.2 实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Dept {
    @NotBlank(message = "部门的名称不能为空", groups = {AddGroup.class})
    private String departName;

    @NotEmpty(message = "人员对象集合不能为空", groups = {AddGroup.class})
    @Size(max = 2, message = "一次添加部门人员最多两个", groups = {AddGroup.class})
    /**
     *    加上该注解能够对嵌套里面的那层对象Person进行校验,要不然不会校验
     */
    @Valid
    private List<Person> personList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {

    @NotNull(message = "id添加时可以为空,更新时不能为空", groups = {UpdateGroup.class})
    private Integer id;

    @NotBlank(message = "名字不能为空", groups = {UpdateGroup.class, AddGroup.class})
    @Size(min = 6, max = 12, message = "名字的长度在6到12之间", groups = {UpdateGroup.class, AddGroup.class})
    private String username;
    @NotNull(message = "年龄不能为空", groups = {UpdateGroup.class, AddGroup.class})
    @Min(value = 20, message = "最小年龄要大于20", groups = {UpdateGroup.class, AddGroup.class})
    private Integer age;

    /**
     * 没有指定组别,默认是Default.class组别
     */
    @NotBlank(message = "编号不能为空")
    @Pattern(regexp = "^(广告组)\\d$", message = "编号要以广告组开头,以1-9结尾")
    private String number;
    /**
     * @see com.tt.enums.PersonStatusEnum
     */
    @NotNull(message = "添加人员的时候状态不能为空", groups = {AddGroup.class})
    private Integer status;
    @NotBlank(message = "添加人员的时候如果状态是类别2的时候角色字段不能为空", groups = {SecondGroup.class})
    private String role;

}

3.6 @Validated 自定义注解校验

如果框架提供的注解不满足实际开发中业务的校验,可以自定义一个校验注解来实现

3.6.1 自定义一个注解

package com.tt.annotation;

/**
 * @Description: 自定义校验注解
 * @Author: ChenYi
 * @Date: 2021/05/17 20:18
 **/

import com.tt.validator.ListNotHasNullValidatorImpl;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RUNTIME)
@Documented
//此处指定了该注解的实现类为ListNotHasNullValidatorImpl
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)
//在同一个地方使用相同的注解会报错,所以该注解是装同一个注解的容器,
@Repeatable(ListRoleNotHasNull.List.class)
public @interface ListRoleNotHasNull {

    /**
     * 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
     */
    int value() default 0;

    /**
     * 默认的报错提示信息
     *
     * @return
     */
    String message() default "List集合中的元素不能含有null元素";

    Class<?>[] groups() default {};

    /**
     * 约束注解的有效负载  将一些元数据信息与该约束注解相关联
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 定义List,为了让Bean的一个属性上可以添加多套规则
     */
    @Target({METHOD, FIELD, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        ListRoleNotHasNull[] value();
    }

}

3.6.2 实现 ConstraintValidator 接口

@Component
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListRoleNotHasNull, List<Role>> {
    private int value;

    @Override
    public void initialize(ListRoleNotHasNull constraintAnnotation) {
        //传入value 值,可以在校验中使用
        this.value = constraintAnnotation.value();
    }

        @Override
        public boolean isValid(List<Role> list, ConstraintValidatorContext constraintValidatorContext) {
            if (list == null || list.size() == 0) {
                return true;
            }
            //返回false就会抛出错误的信息
            for (Role role : list) {
                if (role.getRoleName() == null || role.getId() == null) {
                    return false;
                }
            }
            return true;
        }

}

3.6.3 实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {
    @ListRoleNotHasNull(message = "集合不能为空,并且对象里面的属性值也不能为空", groups = {AddGroup.class})
    private List<Role> roleList;

}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Role {

    private String roleName;

    private Integer id;
}

3.6.4 接口层

@PostMapping("add")
public String definedAdd(@Validated(value = {AddGroup.class}) @RequestBody Company company) {
    return "自定义注解添加成功!";
}

三、@Validated 和 @Valid 的不同

  1. @Valid 用来标记验证属性和方法返回值,支持进行级联和递归校验(可以结合 @Validated 注解在嵌套对象中使用)。
  2. @Validated 是 Spring 提供的注解,提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。
  3. 在 Controller 中校验方法参数时,使用 @Valid 和 @Validated 并无特殊差异(若不需要分组校验的话)。
  4. @Validated 只能用在类、方法和参数上,而 @Valid 可用于类、方法、字段、构造器和参数上。

总结:

建议直接用 @Validated,功能比 @Valid 注解强大

四、后记

本文详细的代码地址(码云仓库):https://gitee.com/caitou123/spring-validated.git

转载自:https://blog.csdn.net/ctycsdn/article/details/117789981

本文作者:Journey&Flower

本文链接:https://www.cnblogs.com/JourneyOfFlower/p/17629454.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Journey&Flower  阅读(3251)  评论(0编辑  收藏  举报
历史上的今天:
2020-08-14 SQLServer的top 100 percent用法
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 Not Found REOL
404 Not Found - REOL
00:00 / 00:00
An audio error has occurred.

Fade away

Do over again

Fade away

Utai hajime no hitomojime

Itsumo mayotteru

Douse toritome no nai koto dakedo

Tsutawaranakya motto imi ga nai (Ooh-ooh, oh-oh-oh)

Doushitatte konna ni fukuzatsu nano ni

Kamikudaite yaranakya tsutawaranai

Hora kekkyoku kashi nanka dou datte ii

Boku no ongaku nanka kono yo ni nakutatte ii nda yo

Ii ndarou

Nee sou darou

Everybody don't know why

Everybody don't know much

Boku wa ki ni shinai, kimi wa kidzukanai

Doko ni mo mou inai inai

Everybody don't know why

Everybody don't know much

Wasureteiku, wasurerareteiku

We don't know, we don't know, no, no

Me no mae, hirogaru genjitsu sekai ga mata yuganda

Nando risetto shite mo

Boku wa boku igai no dareka ni wa umare kawarenai

Sonna no shitteru yo

Ki ni naru ano ko no uwasabanashi mo

Shinikaru hyouteki wa tsugi no sokuhou

Mahi shichatteru (Tteru) kokkara esukeepu (Keepu)

Tooku tooku made ikeru yo

Antei nante nai (Na-na-na-na)

Fuanteina sekai (Na-na-na-na)

Antei nante nai (Na-na-na-na)

Kitto ashita ni wa wasureru yo

Fade away

Do over again

Fade away

Souda sekai wa dokoka ga itsumo uso kusai

Kireigoto dake ja daijina hitotachi sura mamorenai

Kudaranai, bokura minna dokoka kurutteru mitai

Hontou no koto nanka zenbu kamisama mo shiranai

Kamisama mo shiranai (Woah, woah, woah, no, woah)

Kamisama mo shiranai (Woah, woah, woah, no, woah)

Kamisama mo shiranai, but

Kamisama mo shiranai (Woah, no, woah, no, woah)

Everybody don't know why

Everybody don't know much

Boku wa ki ni shinai, kimi wa kidzukanai

Doko ni mo mou inai inai

Everybody don't know why

Everybody don't know much

Wasureteiku, wasurerareteiku

We don't know, we don't know, oh, oh-oh-oh

Ahh, oh-oh-oh-oh

Woah, oh-oh-oh

Ooh, ooh, ooh, ooh-ooh-ooh-ooh