自定义参数校验注解

问题描述

标准的接口开发总是离不了参数校验,Spring自然是为我们准备了相应的模板,使用@NotEmpty、@NotBlank、@NotNull等注解就可对参数进行检查校验,但这些注解必须搭配@Valid使用才能生效。具体可参考:@Valid介绍及相关注解 - 简书 (jianshu.com)

可@Valid使用存在限制,即SpringMVC的Validation只在Controller中生效,从整个接口生态上看,这是合理的设计,数据验证都不通过,也不会到业务层了。

可如果我们需要要在业务层进行数据校验呢?且条件不仅仅是非空,还包括一些特殊的格式或范围要求,使用简单的if-else自然可以解决,但代码也随之复杂且冗余了。

解决办法

spring为我们提供的参数校验无法满足我们的需求,那就自己写注解满足自己的需求。

要求:属性上加上注解就可以实现参数校验,包括非空与指定正则校验,同时返回自定义的错误信息

自定义参数校验注解

参数校验类注解:@Validation

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

/**
 * 参数校验注解
 * 对添加注释的属性进行非空校验与正则校验
 *
 * @author wyhao
 * @date 2021/5/18
 **/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validation {

    //错误信息
    String message() default "参数不能为空";

    //正则表达式
    String pattern() default "";

}

@Validation验证器

package com.example.springtrain.caculate.processor.serviceUtils;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Validation验证器
 * 对有自定义注解@Validation的参数进行校验
 *
 * @author wyhao
 * @date 2021/5/19
 **/
@Slf4j
public class ValidationCheck {

    /**
     * 校验方法
     * 扫描对象中的属性,查看是否有@Validation注解,有注解的进行校验
     *
     * @param o 要校验的对象,如入参对象
     */
    public static void check(Object o) {
        if (null == o) {
            return;
        }
        Class clazz = o.getClass();
        List<Field> fieldList = new ArrayList<Field>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }

        //变量对象的所有属性
        fieldList.forEach(field -> {
            field.setAccessible(true);
            try {
                Object value = field.get(o);

                //获取属性值
                Validation annotation = field.getAnnotation(Validation.class);
                if (null == annotation) {   //未加注解的不做处理
                    return;
                }
                checkNotNull(value, annotation);
                checkPattern(value, annotation);

            } catch (IllegalArgumentException | IllegalAccessException e) {
                log.info("Validation验证器数据解析失败:{}", e.getMessage());
            }
        });
    }

    /**
     * 非空判断
     *
     * @param value      属性值
     * @param validation 注解信息
     */
    private static void checkNotNull(Object value, Validation validation) {
        //log.info("开始校验非空");
        if (validation != null && value == null) {
            throw new RuntimeException(validation.message());
        }
    }

    /**
     * 正则校验
     *
     * @param value      属性值
     * @param validation 注解信息
     */
    private static void checkPattern(Object value, Validation validation) {
        if (null != validation.pattern() && validation.pattern().length() > 0) {//存在正则式
            //将validation中给定的正则表达式编译并赋予给Pattern类
            Pattern p = Pattern.compile(validation.pattern());
            //以p的规则匹配value中的值
            Matcher m = p.matcher(value.toString());
            if (!m.matches()) { //属性值不符合正则所制定的格式,抛出异常
                throw new RuntimeException(validation.message());
            }
        }
    }
}

注解使用

在需要校验的类属性上加上校验规则:

@Data
public class Father {
    @Validation(message = "姓名不可为空")		//非空校验
    private String name;

    @Validation(pattern = "\\d+",message = "年龄只能是数字")	//正则校验,前提当然也是非空
    private String age;

    public Father(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public Father() {
    }
}

首先我们的注解肯定不能影响正常的使用,下面给符合要求的数据,代码正常运行

Father father1 = new Father("Laodie","56");
ValidationCheck.check(father1);	//进行参数校验
System.out.println(father1);

name为空时,下面的代码会直接抛出异常,并提示姓名不可为空

Father father2 = new Father(null,"56");
ValidationCheck.check(father2);	//进行参数校验
System.out.println(father2);

age不符合必须是数字的要求时,会抛出异常并提示年龄只能是数字

Father father3 = new Father(null,"a56");
ValidationCheck.check(father3);
System.out.println(father3);

如果我们还有其他的业务需要,可以在注解中增加相应的属性,在验证器中添加自定义的规则就行,具备一定的可拓展性。

持续优化

每次使用时还需要使用ValidationCheck.check(father1)这样的代码,或许有些麻烦,因为笔者所需要的参数校验地方不多,就偷懒了,其实我们可以再写一个注解,在代码,或直接在类上使用,像这样:

//加在代码上
@ValidationCheck	//进行参数校验
Father father2 = new Father(null,"56");


//或加类上
@ValidationCheck
public class ValidationTest {
    @Test
    public void test() {

        Father father1 = new Father("Laodie","56");
        System.out.println(father1);

        Father father2 = new Father(null,"56");
        System.out.println(father2);

        Father father3 = new Father(null,"a56");
        System.out.println(father3);
    }
}

上一篇:在洛谷3369 Treap模板题 中发现的Splay详解


下一篇:尚硅谷 Docker DockerFile案例 ONBUILD命令案例