1. 程式人生 > >hibernate validator自定義註解實戰之《列舉值校驗》

hibernate validator自定義註解實戰之《列舉值校驗》

前言

在spring專案中,校驗引數功能使用hibernate validator是一個不錯的選擇,我們的專案中也是使用它來進行校驗的,省去了很多難看的校驗邏輯,使程式碼的可讀性也大大增加,本章將帶你使用hibernate validator自定義註解功能實現一個 列舉值校驗的邏輯。

需求

我們先明確下我們的需求,在程式開發過程中,我們經常會有一個物件的屬性值只能出現在一組常量中的校驗需求,例如:使用者性別欄位gender只能等於MALE/FEMALE這兩個其中一個值,使用者賬號的狀態status只能等於:NORMAL/DISABLED/DELETED其中一個等等,那麼我們怎麼能更好的校驗這個引數呢?我們想擁有一個java註解,把它標記在所要校驗的欄位上,當開啟hibernate validator校驗時,就可以校驗其欄位值是否正確。

實現方案

上面提到的一組常量值,我們第一反應應該是定義一個列舉類,儘量不要放在一個統一的constants類下,這樣當系統一旦龐大起來,常量是很難維護和查詢的,所以前期程式碼也應該有一些規範性約束,這裡我們約定一組常量值時使用列舉,並把該列舉類放在對應的類物件裡(以上述所說的使用者功能為例,我們應該把GenerEnum、UserStatusEnum列舉放在User.java下,方便查詢)
這裡我們定義一個叫EnumValue.java的註解類,其下有兩個主要引數一個是enumClass用於指定列舉類,enumMethod指定要校驗的方法,下面我們看程式碼實現。

程式碼實現

package
com.zhuma.demo.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.validation.Constraint; import
javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; import org.assertj.core.util.Strings; /** * @desc 校驗列舉值有效性 * * @author zhumaer * @since 10/17/2017 3:13 PM */ @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = EnumValue.Validator.class) public @interface EnumValue { String message() default "{custom.value.invalid}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; Class<? extends Enum<?>> enumClass(); String enumMethod(); class Validator implements ConstraintValidator<EnumValue, Object> { private Class<? extends Enum<?>> enumClass; private String enumMethod; @Override public void initialize(EnumValue enumValue) { enumMethod = enumValue.enumMethod(); enumClass = enumValue.enumClass(); } @Override public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { if (value == null) { return Boolean.TRUE; } if (enumClass == null || enumMethod == null) { return Boolean.TRUE; } Class<?> valueClass = value.getClass(); try { Method method = enumClass.getMethod(enumMethod, valueClass); if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) { throw new RuntimeException(Strings.formatIfArgs("%s method return is not boolean type in the %s class", enumMethod, enumClass)); } if(!Modifier.isStatic(method.getModifiers())) { throw new RuntimeException(Strings.formatIfArgs("%s method is not static method in the %s class", enumMethod, enumClass)); } Boolean result = (Boolean)method.invoke(null, value); return result == null ? false : result; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException | SecurityException e) { throw new RuntimeException(Strings.formatIfArgs("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass), e); } } } }

備註

  • 自定義註解需要實現ConstraintValidator校驗類,這裡我們定義一個叫Validator的類來實現它,同時實現它下面的兩個方法initialize、isValid,一個是初始化引數的方法,另一個就是校驗邏輯的方法,本例子中我們將校驗類定義在該註解內,用@Constraint(validatedBy = EnumValue.Validator.class)註解指定校驗類,內部邏輯實現比較簡單就是使用了靜態類反射呼叫驗證方法的方式。
  • 對於被校驗的方法我們要求,它必須是返回值型別為Boolean或boolean,並且必須是一個靜態的方法,返回返回值為null時我們認為是校驗不通過的,按false邏輯走。

使用演示

  • 校驗的目標物件類
package com.zhuma.demo.model.po;

import java.io.Serializable;
import java.util.Date;

import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;

import com.zhuma.demo.annotation.EnumValue;
import com.zhuma.demo.validator.CreateGroup;

/**
 * @desc 使用者PO

 * @author zhumaer
 * @since 6/15/2017 2:48 PM
 */
public class User implements Serializable {

    private static final long serialVersionUID = 2594274431751408585L;

    /**
     * 使用者ID
     */
    private Long id;

    /**
     * 登入密碼
     */
    @NotBlank
    private String pwd;

    /**
     * 暱稱
     */
    @NotBlank
    @Length(min=1, max=64)
    private String nickname;

    /**
     * 頭像
     */
    private String img;

    /**
     * 電話
     */
    @Pattern(regexp = "^1[3-9]\\d{9}$")
    private String phone;

    /**
     * 賬號狀態
     */
    @EnumValue(enumClass=UserStatusEnum.class, enumMethod="isValidName")
    private String status;

    /**
     * 最新的登入時間
     */
    private Date latestLoginTime;

    /**
     * 最新的登入IP
     */
    private String latestLoginIp;

    private Date createTime;
    private Date updateTime;

    /**
     * 使用者狀態列舉
     */
    public enum UserStatusEnum {
        /**正常的*/
        NORMAL,
        /**禁用的*/
        DISABLED,
        /**已刪除的*/
        DELETED;

        /**
         * 判斷引數合法性
         */
        public static boolean isValidName(String name) {
            for (UserStatusEnum userStatusEnum : UserStatusEnum.values()) {
                if (userStatusEnum.name().equals(name)) {
                    return true;
                }
            }
            return false;
        }
    }

    //省略getter、setter方法

}
  • controller類
 package com.zhuma.demo.web.user;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.zhuma.demo.model.po.User;

/**
 * @desc 使用者管理控制器
 * 
 * @author zhumaer
 * @since 6/20/2017 16:37 PM
 */
@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User addUser(@Validated @RequestBody User user) {
        user.setId(10000L);
        user.setCreateTime(new Date());
        return user;
    }

}
  • 校驗結果

這裡寫圖片描述

最後

好啦,一個簡單的校驗列舉值的註解功能完成了,如果你有什麼不懂或感覺程式碼上不合理的地方歡迎留言指正O(∩_∩)O~

歡迎關注我們的公眾號或加群,等你哦!