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 相关文章推荐
启动Tomcat时出现大量乱码的解决方法
Jun 21 Java/Android
Java常用工具类汇总 附示例代码
Jun 26 Java/Android
spring项目中切面及AOP的使用方法
Jun 26 Java/Android
实体类或对象序列化时,忽略为空属性的操作
Jun 30 Java/Android
小程序与后端Java接口交互实现HelloWorld入门
Jul 09 Java/Android
gateway与spring-boot-starter-web冲突问题的解决
Jul 16 Java/Android
Java面试题冲刺第十九天--数据库(4)
Aug 07 Java/Android
logback 实现给变量指定默认值
Aug 30 Java/Android
OpenCV实现普通阈值
Nov 17 Java/Android
Android学习之BottomSheetDialog组件的使用
Jun 21 Java/Android
Java服务调用RestTemplate与HttpClient的使用详解
Jun 21 Java/Android
SpringBoot深入分析讲解监听器模式下
Jul 15 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
php htmlentities和htmlspecialchars 的区别
2008/08/18 PHP
PHP图形计数器程序显示网站用户浏览量
2016/07/20 PHP
php数组实现根据某个键值将相同键值合并生成新二维数组的方法
2017/04/26 PHP
JavaScript For Beginners(转载)
2007/01/05 Javascript
用window.location.href实现刷新另个框架页面
2007/03/07 Javascript
Javascript 函数中的参数使用分析
2010/03/27 Javascript
jQuery的运行机制和设计理念分析
2011/04/05 Javascript
js 实现菜单左右滚动显示示例介绍
2013/11/21 Javascript
js每隔5分钟执行一次ajax请求的实现方法
2013/11/27 Javascript
js面向对象编程之如何实现方法重载
2014/07/02 Javascript
AngularJS基础教程之简单介绍
2015/09/27 Javascript
JS对HTML表格进行增删改操作
2016/08/22 Javascript
概述如何实现一个简单的浏览器端js模块加载器
2016/12/07 Javascript
微信小程序 支付功能开发错误总结
2017/02/21 Javascript
webpack源码之loader机制详解
2018/04/06 Javascript
react 国际化的实现代码示例
2018/09/14 Javascript
对vue下点击事件传参和不传参的区别详解
2018/09/15 Javascript
浅谈在vue中使用mint-ui swipe遇到的问题
2018/09/27 Javascript
详解如何制作并发布一个vue的组件的npm包
2018/11/10 Javascript
Windows下Node爬虫神器Puppeteer安装记
2019/01/09 Javascript
初试vue-cli使用HBuilderx打包app的坑
2019/07/17 Javascript
微信小程序关键字变色实现代码实例
2019/12/13 Javascript
vue利用全局导航守卫作登录后跳转到未登录前指定页面的实例代码
2020/05/19 Javascript
[32:47]完美世界DOTA2联赛 GXR vs IO 第二场 11.07
2020/11/09 DOTA
Python读写Redis数据库操作示例
2014/03/18 Python
pycharm使用matplotlib.pyplot不显示图形的解决方法
2018/10/28 Python
python石头剪刀布小游戏(三局两胜制)
2021/01/20 Python
python 实现return返回多个值
2019/11/19 Python
香港No.1得奖零食网:香港零食大王
2016/07/22 全球购物
Java方面的关于数组和继承的笔面试题
2015/09/18 面试题
同学聚会欢迎辞
2014/01/14 职场文书
工作会议方案
2014/05/21 职场文书
初中生300字旷课检讨书
2014/11/19 职场文书
计划生育个人总结
2015/03/02 职场文书
上市公司董事长岗位职责
2015/04/16 职场文书
付款证明模板
2015/06/19 职场文书