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

前言

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

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

像下面这样

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

补充一句话,千万别直接拿着实体类往后传递到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注解了吗

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;@Datapublic 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;@ControllerAdvicepublic 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。咱们先试下错误的

ok,再试下正确的

3.注解中的groups参数详解

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

public interface Group1 {}

建立Group2接口

public interface Group2 {}

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

@Datapublic 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; }

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

接口处也进行标识:

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

test2接口

test3接口

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

总结

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

每一发奋努力的背后,必有加倍的赏赐。

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

相关文章:

你感兴趣的文章:

标签云: