ConstraintValidator类如何实现自定义注解校验前端传参


Posted in Java/Android onJune 18, 2021

前言

今天项目碰到这么一个问题,前端传递的json格式到我的微服务后端转换为vo类,其中有一个Integer的字段后端希望它在固定的几个数里面取值,例如只能取值1、2、4。

一般咱们的思路是啥呢,找一些spring为我们提供的类似@Length、@NotBlank这些注解加在参数上面。

像下面这样

ConstraintValidator类如何实现自定义注解校验前端传参

不过我这个校验一时间想不起来用哪个注解了,咋整呢?行吧,咱不求人,自己实现一个。

补充一句话,千万别直接拿着实体类往后传递到service层校验哈,太low了这样子。

一、利用@Constraint定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {IllegalNumberValidator.class}
)
public @interface IllegalNumber {
    /**
     * 允许前端取的几个值
     * 
     */
    int[] acceptValues();
    /**
     * 标识此字段是否为必选项
     * 
     */
    boolean required() default true;
 
    /**
     * 标识此字段是否为必选项
     * 
     */
    String message() default "数字不合法,不在要求的取值范围之内";
 
    /**
     * 标识要校验的值所属组,这个后面详细解释
     * 
     */
    Class<?>[] groups() default {};
    
    /**
     * 这个字段一般不需要我们关注
     * 
     */
    Class<? extends Payload>[] payload() default {};
}

二、增强注解

1.编写增强类

注意到刚才注解中的@Constraint注解了吗

ConstraintValidator类如何实现自定义注解校验前端传参

validatedBy属性标识这个注解要被哪个类所增强,我们把增强类IllegalNumberValidator定义出来

import com.google.common.collect.Lists;
import org.springframework.util.StringUtils;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IllegalNumberValidator implements ConstraintValidator<IllegalNumber, Integer> {
    private final List<Integer> valueList = Lists.newArrayList();
    private boolean require = false;
    @Override
    public void initialize(IllegalNumber constraintAnnotation) {
        require = constraintAnnotation.required();
        int[] ints = constraintAnnotation.acceptValues();
        for (int anInt : ints) {
            valueList.add(anInt);
        }
    }
    @Override
    public boolean isValid(Integer number, ConstraintValidatorContext constraintValidatorContext) {
        // 如果是必选的话,假设为我们传递的参数为空那肯定不行
        if (require) {
            if (number == null) {
                return false;
            }
            return valueList.contains(number);
        } else {
            // 如果不为必选参数,为空返回true,不为空还是得校验
            if (StringUtils.isEmpty(number)) {
                return true;
            } else {
                return valueList.contains(number);
            }
        }
    }
}

增强类继承ConstraintValidator类,实现的initialize()方法是初始化方法,啥意思呢,啥目的呢?在你真正执行校验之前,可以做一些准备性工作,发生在要校验的值上面的注解的IllegalNumber 已经给咱们传进来了。我做的初始化工作就是load一下Integer类型的可选值,方便一会执行真正的校验。

然后在isValid()方法中你可以做真正的校验了,很简单,我看下传递的Integer类型的值是不是acceptValues里面的可选值就行了。

定义一个前端传递的类,方便调试注解

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
@Data
public class TestVO {
    @NotNull
    @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "请正确取值")
    private Integer number;
    @NotNull
    @Length(min = 1)
    private String password;
}

定义接口,用来接收前端传递的json数据并parse为TestVO类

/**
     * 测试自定义注解
     *
     * @param vo json将会映射的实体
     * @return 默认信息
     */
    @PostMapping(value = "/v1.0/test2", name = "测试自定义注解")
    public String test2(@Valid @RequestBody TestVO vo) {
        log.info("get vo success , detail message is:{}", vo);
        return RETURN_MESSAGE;
    }

注意,如果说前端传递数据不符合注解的校验,其实是会抛出异常的来自@Constraint注解实现的注解都有此特点,例如@Length、@Max等。咱们需要在异常抛出的时候给出拦截 这里咱们做一个通用拦截:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.Objects;
import javax.validation.ConstraintViolationException;
@ControllerAdvice
public class RestResponseEntityExceptionHandler {
    private static final Logger LOG = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
    @Autowired
    private ApplicationContext applicationContext;
    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleConstraintViolationException(ConstraintViolationException e) {
        LOG.info("ConstraintViolationException intercept success:{}", e.getMessage());
        return e.getMessage();
    }
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        LOG.info("MethodArgumentNotValidException intercept success:{}", e.getMessage());
        return Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
    }
}

2.测试效果

下面测试一下。打开postman。直接干!取值的限定是0、1、2。咱们先试下错误的

ConstraintValidator类如何实现自定义注解校验前端传参

ok,再试下正确的

ConstraintValidator类如何实现自定义注解校验前端传参

3.注解中的groups参数详解

groups参数,代表所属组的意思。演示下怎么用,大家也就知道这个参数啥意思了。 建立Group1接口

public interface Group1 {
}

建立Group2接口

public interface Group2 {
}

给TestVO增加一个参数,方便一会进行比较

@Data
public class TestVO {
    @NotNull
    @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "请正确取值",groups = Group1.class)
    private Integer number;
    @NotNull
    @IllegalNumber(acceptValues = {0, 1,2},required = true,message = "请正确取值ha",groups = Group2.class)
    private Integer number2;
    @NotNull
    @Length(min = 1)
    private String password; 
}

使用注解的时候标明所属组:

ConstraintValidator类如何实现自定义注解校验前端传参

接口处也进行标识:

ConstraintValidator类如何实现自定义注解校验前端传参

现在咱们分别测试下两个接口,看groups参数是否能生效

test2接口

ConstraintValidator类如何实现自定义注解校验前端传参

test3接口

ConstraintValidator类如何实现自定义注解校验前端传参

ok,相信大家对此参数已经掌握了,这里不再多余赘述。

总结

本篇介绍了自定义注解的另外一种手法,其实还有许许多多的手法,例如利用反射实现、利用拦截器实现等等。遇见的时候咱们再介绍。 以上仅为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木

Java/Android 相关文章推荐
springboot中一些比较常用的注解总结
Jun 11 Java/Android
Spring Data JPA使用JPQL与原生SQL进行查询的操作
Jun 15 Java/Android
jackson json序列化实现首字母大写,第二个字母需小写
Jun 29 Java/Android
解决Swagger2返回map复杂结构不能解析的问题
Jul 02 Java/Android
java基础——多线程
Jul 03 Java/Android
java泛型通配符详解
Jul 25 Java/Android
spring cloud 配置中心客户端启动遇到的问题
Sep 25 Java/Android
JVM的类加载器和双亲委派模式你了解吗
Mar 13 Java/Android
SpringCloud Feign请求头删除修改的操作代码
Mar 20 Java/Android
Flutter Navigator 实现路由传递参数
Apr 22 Java/Android
使用Postman测试需要授权的接口问题
Jun 21 Java/Android
JDK8中String的intern()方法实例详细解读
Sep 23 Java/Android
SpringBoot项目中控制台日志的保存配置操作
Jun 18 #Java/Android
浅谈@Value和@Bean的执行顺序问题
Jun 16 #Java/Android
SpringBoot2 参数管理实践之入参出参与校验的方式
Jun 16 #Java/Android
SpringBoot生成License的实现示例
Springboot如何使用logback实现多环境配置?
解决tk mapper 通用mapper的bug问题
一篇带你入门Java垃圾回收器
You might like
咖啡语言
2021/03/03 咖啡文化
用PHP调用数据库的存贮过程
2006/10/09 PHP
无法载入 mcrypt 扩展,请检查 PHP 配置终极解决方案
2011/07/18 PHP
基于php在各种web服务器的运行模式详解
2013/06/03 PHP
学习PHP Cookie处理函数
2016/08/09 PHP
Yii框架学习笔记之应用组件操作示例
2019/11/13 PHP
File文件控件,选中文件(图片,flash,视频)即立即预览显示
2009/04/09 Javascript
非常有用的40款jQuery 插件推荐(系列二)
2011/12/25 Javascript
javascript中IE浏览器不支持NEW DATE()带参数的解决方法
2012/03/01 Javascript
jQuery div层的放大与缩小简单实现代码
2013/03/28 Javascript
通过jquery 获取URL参数并进行转码
2014/08/18 Javascript
JavaScript 学习笔记之语句
2015/01/14 Javascript
jquery判断当前浏览器的实现代码
2015/11/07 Javascript
详解javascript高级定时器
2015/12/31 Javascript
jQuery的图片轮播插件PgwSlideshow使用详解
2016/08/11 Javascript
使用JS批量选中功能实现更改数据库中的status状态值(批量展示)
2016/11/22 Javascript
详解vue项目的构建,打包,发布全过程
2017/11/23 Javascript
Vue 报错TypeError: this.$set is not a function 的解决方法
2018/12/17 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
Vuex的API文档说明详解
2020/02/05 Javascript
使用Python编写提取日志中的中文的脚本的方法
2015/04/30 Python
Python API 自动化实战详解(纯代码)
2019/06/11 Python
python绘制直方图和密度图的实例
2019/07/08 Python
python内存动态分配过程详解
2019/07/15 Python
Python  Django 母版和继承解析
2019/08/09 Python
Scrapy基于scrapy_redis实现分布式爬虫部署的示例
2020/09/29 Python
网络工程专业毕业生推荐信
2013/10/28 职场文书
营业员实习自我鉴定
2013/12/07 职场文书
应聘自荐信
2013/12/14 职场文书
开办化妆品公司创业计划书
2013/12/26 职场文书
巡警年度自我鉴定
2014/02/21 职场文书
幼儿园优秀班主任事迹材料
2014/05/14 职场文书
2014教师党员自我评议总结
2014/09/19 职场文书
2014年个人技术工作总结
2014/12/08 职场文书
基于python的matplotlib制作双Y轴图
2021/04/20 Python
Python Django / Flask如何使用Elasticsearch
2022/04/19 Python