實現註解校驗Dto欄位是否為空

lovefoolself發表於2024-10-30

一、背景

我們用json物件作為接收引數的包裝器,最後要轉化為dto進行業務操作,操作之前要做非空校驗,我們可以實現2個註解來實現這個通用的操作。@NotNullField @CheckNull

二、思路

1.實現@NotNullField註解,註解標記在dto欄位名上面

@Target(ElementType.FIELD) // 目標為欄位
@Retention(RetentionPolicy.RUNTIME) // 執行時可用
public @interface NotNullField {
    String fieldName() default ""; // 可自定義欄位名
    String message() default "欄位不能為空"; // 預設錯誤訊息
}

image
2.實現@CheckNull註解,註解標記在呼叫方法上面,這個是要接受dto.class的
image

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNull {
    //要校驗的dto
    Class<?> value();
}

三、程式碼實現

1.@NotNullField,這個我實現了一個靜態方法

 /**
     * 校驗標註了非空欄位的dto
     * @param obj
     * @throws NullParamsException
     */
    public static void validateNotNull(Object obj) throws NullParamsException {

        Class<?> clazz = obj.getClass();

        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(NotNullField.class)) {
                    field.setAccessible(true); // 允許訪問私有欄位
                    try {
                        Object value = field.get(obj);
                        if (value == null) {
                            NotNullField annotation = field.getAnnotation(NotNullField.class);
                            throw new NullParamsException(
                                    String.format("驗證錯誤: %s - %s", annotation.fieldName(), annotation.message())
                            );
                        }else{
                            //如果是集合型別遞迴呼叫
                            //感覺遞迴呼叫 是一個危險的方式 大資料量會導致棧記憶體溢位 先進行實驗
                            //程式碼中如果有集合的方式必須 業務自己提取出來data自己去遍歷校驗
                            if(Collections.class.isAssignableFrom(field.getType())){
                                field.setAccessible(true); // 允許訪問私有欄位
                                Collection<?> collection = (Collection<?>) value;
                                Iterator<?> iterator = collection.iterator();
                                while (iterator.hasNext()) {
                                    Object item = iterator.next();
                                    // 處理集合中的每個物件
                                    validateNotNull(item);
                                }
                            }
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("訪問錯誤", e);
                    }
                }
            }
            // 移動到父類 要支援繼承
            clazz = clazz.getSuperclass();
        }
    }

2.@CheckNull,這裡的@Order 註解是標記註解起作用的順序,因為我還寫了其他校驗註解

@Component
@Aspect
@Order(2)
public class CheckNullAspect {
    @Autowired
    private MyAutoConfig myAutoConfig;
    /**
     * 環繞處理
     * 連線點
     * 註解的全限定名稱
     * @return 結果
     * @throws Throwable 異常
     */
    @Pointcut(value = "@annotation(具體包)")
    public void checkAround() throws Throwable {

    }

    @Before("checkAround()")
    public void around(JoinPoint proceedingJoinPoint) throws Throwable {
        // 從切面織入點處透過反射機制獲取織入點處的方法
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //獲取切入點所在的方法
        Method method = signature.getMethod();
        //獲取操作
        CheckNull annotation = method.getAnnotation(CheckNull.class);
        Class<?> dtoClass = annotation.value();
        // 獲取入參
        Object[] objects = proceedingJoinPoint.getArgs();

        if (Objects.nonNull(objects) && objects.length > 0) {
            JSONObject jsonObject = (JSONObject) objects[0];
            Object dto = jsonObject.toJavaObject(dtoClass);
            CheckUtil.validateNotNull(dto);
        }
    }
}

四、後記

自定義註解實現在spring型別的專案裡面超好用!

相關文章