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