天天看點

自定義參數校驗注解

問題描述

标準的接口開發總是離不了參數校驗,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);
    }
}