锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. springboot自定义validation注解:多字段属性关联校验

springboot自定义validation注解:多字段属性关联校验

0
  • JAVA
  • 发布于 2024-08-14
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

背景 

validation 中提供的注解都是针对单个参数的,如果两个参数之间有关联关系就只能在代码里判断了,比如:

@Data
public class TestPo {
 
    
    private String type;
 
    //当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
    private Date sendTime;
 
    //当type为草稿时,sendContent可以为空,否则必须有值
    private SendContent sendContent;
}

这种就只能在代码中判断 type 的值然后决定另外两个参数的校验。

方法 1 使用 @ScriptAssert 注解

@Data
@ScriptAssert.List(value = {
        @ScriptAssert(script = "_this.type == '定时发送' && _this.sendTime != null",
                lang = "javascript",
                message = "当type为定时发送时,必须填写发送时间"),
        @ScriptAssert(script = "_this.type != '草稿' && _this.sendContent != null",
                lang = "javascript",
                message = "当type不是草稿时,sendContent必填")
})
public class TestPo {
 
    @NotBlank(message = "type不能为空")
    private String type;
 
    //当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
    private Date sendTime;
 
    @Valid
    //当type为草稿时,sendContent可以为空,否则必须有值
    private SendContent sendContent;
}

这种方法需要使用 javascript,对于部分人来说可能不够直观也很难调试。

方法 2 自定义注解 +spring 表达式

针对这种情况我利用 spring 表达式写了一个自定义注解来解决这个问题。

第一步:自定义注解

/**
 * 多属性关联校验注解
 * 用于校验多个属性之间的关联关系
 * 当when条件满足时,必须满足must条件否则校验不通过
 * 注意:如果解析spel表达式错误将抛出异常
 * @author wangzhen
 */
@Documented
@Constraint(validatedBy = {MultiFieldAssociationCheckValidator.class })
@Target({TYPE_USE })
@Retention(RUNTIME)
@Repeatable(MultiFieldAssociationCheck.List.class)
public @interface MultiFieldAssociationCheck {
 
    /**
     * 错误信息描述,必填
     */
    String message();
 
    /**
     * 分组校验
     */
    Class<?>[] groups() default { };
 
    /**
     * 负载
     */
    Class<? extends Payload>[] payload() default { };
 
    /**
     * 当什么条件下校验,必须是一个spel表达式
     */
    String when();
 
    /**
     * 必须满足什么条件,必须是一个spel表达式
     */
    String must();
    @Target({TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        MultiFieldAssociationCheck[] value();
    }
}

第二步:编写注解校验类

/**
 * 多属性关联校验注解的实现类
 */
public class MultiFieldAssociationCheckValidator implements ConstraintValidator<MultiFieldAssociationCheck, Object> {
 
    private static final String SPEL_TEMPLATE = "%s%s%s";
    private static final String SPEL_PREFIX = "#{";
    private static final String SPEL_SUFFIX = "}";
    private String when;
 
    private String must;
    @Override
    public void initialize(MultiFieldAssociationCheck constraintAnnotation) {
        this.when = constraintAnnotation.when();
        this.must = constraintAnnotation.must();
    }
 
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
 
        if (StringUtils.isBlank(when) || StringUtils.isBlank(must)) {
            return true;
        }
        Map<String, Object> spelMap = getSpelMap(value);
        //when属性是一个spel表达式,执行这个表达式可以得到一个boolean值
        boolean whenCheck = Boolean.parseBoolean(SpelUtils.parseSpel(String.format(SPEL_TEMPLATE, SPEL_PREFIX, when, SPEL_SUFFIX), spelMap));
        if (whenCheck) {
            //判断must是否满足条件
            boolean mustCheck = Boolean.parseBoolean(SpelUtils.parseSpel(String.format(SPEL_TEMPLATE, SPEL_PREFIX, must, SPEL_SUFFIX), spelMap));
            if (!mustCheck) {
                //获取注解中的message属性值
                String message = context.getDefaultConstraintMessageTemplate();
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
                return false;
            }
        }
        return true;
    }
 
 
 
    @SneakyThrows
    private Map<String,Object> getSpelMap(Object value){
        Field[] declaredFields = value.getClass().getDeclaredFields();
        Map<String,Object> spelMap = new HashMap<>();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            //将对象中的属性名和属性值放入map中
            spelMap.put(declaredField.getName(),declaredField.get(value));
        }
        return spelMap;
    }

类中依赖的 SpelUtils.parseSpel 方法

public static String parseSpel( String spel, Map<String, Object> map) {
        if (StringUtils.isBlank(spel)) {
            return "";
        } else {
            ExpressionParser parser = new SpelExpressionParser();
            StandardEvaluationContext context = new StandardEvaluationContext();
            context.setVariables(map);
            context.addPropertyAccessor(new MapAccessor());
            context.addPropertyAccessor(new BeanFactoryAccessor());
            return (String)parser.parseExpression(spel, new TemplateParserContext()).getValue(context, String.class);
        }
    }

第三步:使用注解,这个注解使用在类上

@Data
@MultiFieldAssociationCheck.List(
       value = {
               @MultiFieldAssociationCheck(when = "#type.equals('定时发送')", must = "#sendTime != null",message = "当type为定时发送时,必须填写发送时间"),
               @MultiFieldAssociationCheck(when = "!#type.equals('草稿')", must = "#sendContent != null",message = "当type为不是草稿时,sendContent必须有值"),
               @MultiFieldAssociationCheck(when = "#type.equals('立即发送')", must = "'123'.equals(#sendContent.content)",message = "当type为立即发送时,sendContent的content属性必须为123")
       }
)
public class TestPo {
 
    private String type;
 
    //当type为定时发送时,必须填写发送时间,当type为立即发送时,可以不填发送时间
    private Date sendTime;
 
    @Valid
    //当type为草稿时,sendContent可以为空,否则必须有值
    private SendContent sendContent;
}

注意:spring 表达式平时不太常用,一定要好好检查避免出现异常。

标签: #软件开发 1171 #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.