hibernate validator自定义校验注解以及基于服务(服务组)的校验

hibernate validator是Bean Validation 1.1 (JSR 349) Reference Implementation,其广泛的应用在mvc的参数校验中,尤其是使用服务端spring mvc模板的时候。在这里,我们要讲的不是如何使用的问题。而是如何基于其提供更加符合项目要求以及最小化重复实现的目标,在不少情况下,我们在不同的服务中,对于相同的请求类Req,对于其中不同字段的校验要求是不同的,比如有些时候name字段是必须的,但其他情况下是非必须的,所以需要跟着服务或者服务组进行校验。再如,几乎每个系统都会使用到数据字典,使用数据字典的时候,有两种方式可以校验其取值范围,一种是直接使用java枚举类型,另外一种是人工进行判断。只不过,我们不建议使用枚举类型,但是我们也希望能够和通用的参数一样进行校验,而不是对于数据字典进行特殊校验。对于这两种情况,都可以在hibernate validator的技术上实现。对于服务分组,可以新增一个注解比如ValidServices实现,对于枚举校验,可以增加一个自定义的校验注解实现,如下:

ValidServices.java

package tf56.lf.base.metadata.validate;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
/**
 * 参数校验分组注解
 * @author admin
 *
 */
@Target({ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ValidServices {
    String[] services();
}
package tf56.lf.base.metadata.validate;

import java.util.Map;

public class ValidationResult {
    // 校验结果是否有错
    private boolean success = true;

    // 校验错误信息
    private Map<String, String> errorPair;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Map<String, String> getErrorPair() {
        return errorPair;
    }

    public void setErrorPair(Map<String, String> errorPair) {
        this.errorPair = errorPair;
    }
}

Dict.java

package tf56.lf.base.metadata.validate;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { DictValidator.class })
@Documented
public @interface Dict {
    
    String dictName();
    
    String message() default "{数据字典取值不合法,请参考标准数据字典管理}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

DictValidator:

package tf56.lf.base.metadata.validate;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import tf56.lf.base.metadata.cons.DictUtils;

public class DictValidator implements ConstraintValidator<Dict, String> {
    
    private String dictName;
    
    @Override
    public void initialize(Dict dictAnno) {
        this.dictName = dictAnno.dictName();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (DictUtils.isValid(dictName, value)) {
            return true;
        }
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate("该字段的当前值" + value + "不在数据字典" + dictName + "的有效取值范围内, 有效值为:[" + DictUtils.getDictKeys(dictName) + "]").addConstraintViolation();
        return false;
    }

}
DictUtils为字典取值范围校验类,每个公司的实现不同,读者自己构建一个即可。

主类:

package tf56.lf.common.util;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.groups.Default;

import org.apache.commons.collections.CollectionUtils;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tf56.lf.base.metadata.validate.Dict;
import tf56.lf.base.metadata.validate.ValidServices;
import tf56.lf.base.metadata.validate.ValidationResult;
import tf56.lf.common.cons.SpiderSystemError;
import tf56.lf.common.exception.LfException;

public class ValidationUtils {
    
    private static Map<Field,Set<String>> validFields = new ConcurrentHashMap<Field,Set<String>>();
    
    static final Logger logger = LoggerFactory.getLogger(ValidationUtils.class);
    
    private static Validator validator = Validation
            .buildDefaultValidatorFactory().getValidator();

    public static <T> ValidationResult validateEntity(String serviceId,T obj) {
        boolean noNeedCheck = true;
        Map<String, String> errorMsg = new HashMap<String, String>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for(int i=0;i<fields.length;i++) {
            if(validFields.get(fields[i]) == null) {
                Set<String> services = new HashSet<String>();
                ValidServices serviceAnno = fields[i].getAnnotation(ValidServices.class);
                if (serviceAnno != null) {
                    for (int j=0;j<serviceAnno.services().length;j++) {
                        services.add(String.valueOf(serviceAnno.services()[j]));
                    }
                }
                validFields.putIfAbsent(fields[i], services);
            }
            if (validFields.get(fields[i]).isEmpty() || validFields.get(fields[i]).contains(serviceId)) {
                noNeedCheck = false;
                Map<String, String> errorPair = validatePropertyInternal(serviceId,obj,fields[i].getName());
                errorMsg.putAll(errorPair);
            }
        }
        if (noNeedCheck) {
            logger.warn("服务" + serviceId + "在" + obj.getClass().getCanonicalName() + "中所有字段都没有配置做任何校验.");
        }
        ValidationResult result = new ValidationResult();
        if (!errorMsg.isEmpty()) {
            result.setErrorPair(errorMsg);
            result.setSuccess(false);
        }
        return result;
    }
    
    private static <T> Map<String, String> validatePropertyInternal(String serviceId, T obj,
            String propertyName) {
        Set<ConstraintViolation<T>> set = validator.validateProperty(obj,
                propertyName, Default.class);
        Map<String, String> errorMsg = new HashMap<String, String>();
        if (CollectionUtils.isNotEmpty(set)) {
            for (ConstraintViolation<T> cv : set) {
                errorMsg.put(propertyName, cv.getMessage());
            }
        }
        return errorMsg;
    }

    public static <T> ValidationResult validateProperty(String serviceId, T obj,
            String propertyName) {
        ValidationResult result = new ValidationResult();
        Field field = null;
        try {
            field = obj.getClass().getDeclaredField(propertyName);
        } catch (NoSuchFieldException | SecurityException e) {
            throw new LfException(SpiderSystemError.ERR_NO_SUCH_FIELD_OR_FORBIDDEN);
        }
        if(validFields.get(field) == null) {
            Set<String> services = new HashSet<String>();
            ValidServices serviceAnno = field.getAnnotation(ValidServices.class);
            if (serviceAnno != null) {
                for (int i=0;i<serviceAnno.services().length;i++) {
                    services.add(String.valueOf(serviceAnno.services()[i]));
                }
            }
            validFields.putIfAbsent(field, services);
        }
        if (validFields.get(field).isEmpty() || validFields.get(field).contains(serviceId)) {
            Map<String, String> errorPair = validatePropertyInternal(serviceId,obj,field.getName());
            if (!errorPair.isEmpty()) {
                result.setErrorPair(errorPair);
                result.setSuccess(false);
            }
        }
        return result;
    }
    
    public static void main(String[] args) {
        SimpleEntity entity = new SimpleEntity();
        entity.setValid(true);
        ValidationResult result = ValidationUtils.validateEntity("1001",entity);
        if (!result.isSuccess()) {
            System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
        }
        
        result = ValidationUtils.validateEntity("100",entity);
        if (!result.isSuccess()) {
            System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
        }
        
        entity = new SimpleEntity();
        entity.setValid(true);
        result = ValidationUtils.validateEntity("1",entity);
        if (!result.isSuccess()) {
            System.out.println(FastJsonUtil.serializeFromObject(result.getErrorPair()));
        }
    }
    
    public static class SimpleEntity {

        @ValidServices(services = { "1001","1002" })
        @NotBlank(message="名字不能为空或者空串")
        @Length(min=2,max=10,message="名字必须由2~10个字组成")
        private String name;
        
        @Dict(dictName = "payType")
        private String payType;
        
        @Past(message="时间不能晚于当前时间")
        private Date date;
        
        @Email(message="邮箱格式不正确")
        private String email;
        
        @Pattern(regexp="(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{5,10}",message="密码必须是5~10位数字和字母的组合")
        private String password;
        
        @AssertTrue(message="字段必须为真")
        private boolean valid;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Date getDate() {
            return date;
        }

        public void setDate(Date date) {
            this.date = date;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public boolean isValid() {
            return valid;
        }

        public void setValid(boolean valid) {
            this.valid = valid;
        }
    }
}

输出如下:

{"name":"名字不能为空或者空串","payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}
{"payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}
{"payType":"该字段的当前值null不在数据字典payType的有效取值范围内, 有效值为:[OTHER,BANK,CASH,TF_ACCOUNT]"}

原文地址:https://www.cnblogs.com/zhjh256/p/7078031.html