Validation校验框架的自定义注解

前言:

最近单位在新开发一个系统,我主要负责订单模块的开发,这个模块的数据结构嵌套层数比较多,如果自己写一个校验方法,校验比较麻烦。所以,我选择了Validation校验框架。技术是一把双刃剑,Validation对非空,数据大小,长度这样简单的校验一个注解就可以搞定。但是对一些复杂的校验,明显感觉不是特别友好。Validation也对复杂的校验提供了自定义注解的解决方案,但是,可能还有一些复杂的校验Validation不能实现,这些就需要换另一些方式去实现。

正文:

一、Validation中的注解:

1.Bean Validation 的注解:

注解

作用

@Valid

被注释的元素是一个对象,需要检查此对象的所有字段值

@Null

被注释的元素必须为 null

@NotNull

被注释的元素必须不为 null

@AssertTrue

被注释的元素必须为 true

@AssertFalse

被注释的元素必须为 false

@Min(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max, min)

被注释的元素的大小必须在指定的范围内

@Digits (integer, fraction)

被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past

被注释的元素必须是一个过去的日期

@Future

被注释的元素必须是一个将来的日期

@Pattern(value)

被注释的元素必须符合指定的正则表达式

2.Hibernate Validator的注解:

注解

作用

@Email被注释的元素必须是电子邮箱地址
@Length(min=, max=)被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range(min=, max=)被注释的元素必须在合适的范围内
@NotBlank被注释的字符串的必须非空
@URL(protocol=,
host=,    port=, 
regexp=, flags=)
被注释的字符串必须是一个有效的url
@CreditCardNumber被注释的字符串必须通过Luhn校验算法,
银行卡,信用卡等号码一般都用Luhn
计算合法性

3. @Validated与@Valid的区别:

规范:@Validated(Spring's JSR-303 规范,是标准 JSR-303 的一个变种),javax提供了@Valid(标准JSR-303规范);

支持分组:@Validated支持分组,而@Valid不支持;

注解使用场景:@Validated 可以用在类型、方法和方法参数上。但不能用在成员属性上 ; @Valid 可以用在方法、构造函数、方法参数和成员属性上。

此外:@Valid支持嵌套校验

4. @NotNull、@NotEmpty、@NotBlank的区别:

@NotNull:任何对象的value不能为null

@NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null

@NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0

二、自定义注解的使用:

Validation框架的自定义注解实现必要容易,主要是分两步:第一步,自定义一个注解类,第二步,重写校验器。下面,我用一个支持校验枚举类范围的自定义注解来具体来说一下:

0.自定义一个枚举类(订单类型枚举类):

package com.hanxiaozhang.order.constant;

/**
 * 〈一句话功能简述〉<br>
 * 〈订单类型枚举类〉
 *
 * @author hanxinghua
 * @create 2020/7/22
 * @since 1.0.0
 */
public enum OrderTypeEnum {

    //订单类型:0:日用品、1:服装、9:其他
    DAILY_NEED(0, "日用品"),
    CLOTHING(1, "服装"),
    OTHER(9, "其他");

    private Integer code;

    private String name;

    OrderTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }


    public String getName() {
        return name;
    }

    /**
     * 校验Code范围
     *
     * @param code
     * @return
     */
    public static boolean isValidCode(Integer code) {

        if (code == null) {
            return false;
        }

        for (OrderTypeEnum status : OrderTypeEnum.values()) {
            if (status.getCode().equals(code)) {
                return true;
            }
        }
        return false;
    }

}

1.自定义枚举范围校验的注解:

package com.hanxiaozhang.validation.annotation;



import com.hanxiaozhang.validation.validator.EnumRangeValidator;

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

/**
 * 功能描述: <br>
 * 〈枚举类范围校验器注解〉
 *
 * @Author:hanxinghua
 * @Date: 2020/7/20
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = EnumRangeValidator.class)  // 校验器
public @interface EnumRange {

    /**
     * 默认提示消息
     *
     * @return
     */
    String message() default "类型错误!";

    /**
     * 分组
     *
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * 负载
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 枚举类的Class
     *
     * @return
     */
    Class<? extends Enum<?>> enumClass();

    /**
     * 校验方法
     *
     * @return
     */
    String enumValidMethod();

}

2.自定义枚举范围校验的校验器:

package com.hanxiaozhang.validation.validator;


import com.hanxiaozhang.validation.annotation.EnumRange;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * 〈一句话功能简述〉<br>
 * 〈枚举类范围校验器〉
 *
 * @author hanxinghua
 * @create 2020/7/20
 * @since 1.0.0
 */
public class EnumRangeValidator implements ConstraintValidator<EnumRange, Object> {

    private Class<? extends Enum<?>> enumClass;
    private String enumValidMethod;

    @Override
    public void initialize(EnumRange enumRange) {
        enumClass = enumRange.enumClass();
        enumValidMethod = enumRange.enumValidMethod();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        if (value == null) {
            return true;
        }

        if (enumClass == null || enumValidMethod == null) {
            return true;
        }

        Class<?> valueClass = value.getClass();

        try {
            // 反射获取方法
            Method method = enumClass.getMethod(enumValidMethod, valueClass);
            // 校验 enumValidMethod 是否为 boolean类型方法
            if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
                throw new RuntimeException(enumValidMethod+" method return is not boolean type in the "+enumClass+" class");
            }
            // 校验 enumValidMethod 是否为 静态类型方法
            if(!Modifier.isStatic(method.getModifiers())) {
                throw new RuntimeException(enumValidMethod+ " method is not static method in the "+enumClass+" class");
            }
            // 反射调用该方法
            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("This "+enumValidMethod+"( "+valueClass+" ) method does not exist in the "+enumClass, e);
        }
    }


}

3.使用:

package com.hanxiaozhang.order.entity;

import com.hanxiaozhang.order.constant.OrderTypeEnum;
import com.hanxiaozhang.validation.annotation.EnumRange;
import com.hanxiaozhang.validation.annotation.PriceNotEmpty;
import com.hanxiaozhang.validation.group.CreateGroupValidation;
import com.hanxiaozhang.validation.group.UpdateGroupValidation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;

/**
 * 〈一句话功能简述〉<br>
 * 〈OrderDTO〉
 *
 * @author hanxinghua
 * @create 2020/7/22
 * @since 1.0.0
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Order {

    private Long id;

    @Valid
    @NotBlank(message = "订单名称不能为空!",groups = {CreateGroupValidation.class, UpdateGroupValidation.class})
    private String orderName;

    @Valid
    @NotBlank(message = "订单编号不能为空!",groups = {UpdateGroupValidation.class})
    private String orderNo;

    @EnumRange(message = "订单类型错误!",enumClass = OrderTypeEnum.class, enumValidMethod ="isValidCode",groups = {CreateGroupValidation.class, UpdateGroupValidation.class} )
    @NotNull(message = "订单类型为空!",groups = {CreateGroupValidation.class, UpdateGroupValidation.class})
    private Integer type;

    private Integer number;

    @PriceNotEmpty(groups = {CreateGroupValidation.class, UpdateGroupValidation.class})
    private BigDecimal price;

}

三、特别说明:

1.校验器isValid()方法ConstraintValidatorContext的使用:

ConstraintValidatorContext在这个环境上下文中,我们可以获取当前校验对象属性的父节点,即当前对象的所有信息。但是有些情况获取不到,获取当前对象所有信息的方法如下:

ConstraintValidatorContextImpl constraintValidatorContext = (ConstraintValidatorContextImpl)context;
        List<ConstraintViolationCreationContext> constraintViolationCreationContexts = constraintValidatorContext.getConstraintViolationCreationContexts();
        ConstraintViolationCreationContext constraintViolationCreationContext = constraintViolationCreationContexts.get(0);
        Object object = constraintViolationCreationContext.getPath().getLeafNode().getParent().getValue();
        Order order = (Order) object;

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hanxiaozhang2018

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值