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

时间:2022-09-01 10:26:44

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]"}

hibernate validator自定义校验注解以及基于服务(服务组)的校验的更多相关文章

  1. 自定义jsr-269注解处理器 Error&colon;服务配置文件不正确&comma;或构造处理程序对象javax&period;annotation&period;processing&period;Processor&colon; Provider not found

    出现的原因 自定义处理器还没有被编译就被调用,所以报 not found在根据配置寻找自定义的注解处理器时,自定义处理器还未被编译12解决方式 maven项目可以配置编译插件,在编译项目之前先编译处理 ...

  2. hibernate validator参数校验&amp&semi;自定义校验注解

    参数校验:简单的就逐个手动写代码校验,推荐用Valid,使用hibernate-validator提供的,如果参数不能通过校验,报400错误,请求格式不正确: 步骤1:在参数对象的属性上添加校验注解如 ...

  3. SpringMVC--使用hibernate validator数据校验

    JSR 303 Spring3开始支持JSR 303 验证框架,JSR303是Java为Bean数据合法性校验所提供的标准框架.JSR 303 支持XML和注解风格的验证,通过在Bean属性上标注类似 ...

  4. hibernate validator【原】

    hibernate validator 功能 在开发中经常做一些字段校验的功能,比如非空,长度限制,邮箱验证等等,为了省掉这种冗长繁琐的操作,hibernate validator提供了一套精简的注释 ...

  5. Hibernate Validator实践之一 入门篇

    在后台的业务逻辑中,对数据值的校验在各层都存在(展示层,业务层,数据访问层等),并且各层校验的规则又不尽相同,如下图所示 注:该图片来自于Hibernate Validator官网 在各层中重复的校验 ...

  6. spring boot 1&period;4默认使用 hibernate validator

    spring boot 1.4默认使用 hibernate validator 5.2.4 Final实现校验功能.hibernate validator 5.2.4 Final是JSR 349 Be ...

  7. 用spring的&commat;Validated注解和org&period;hibernate&period;validator&period;constraints&period;&ast;的一些注解在后台完成数据校验

    这个demo主要是让spring的@Validated注解和hibernate支持JSR数据校验的一些注解结合起来,完成数据校验.这个demo用的是springboot. 首先domain对象Foo的 ...

  8. springboot使用hibernate validator校验

    一.参数校验 在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要 ...

  9. springboot使用hibernate validator校验方式

    一.参数校验 在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要 ...

随机推荐

  1. mysql实现分组和组内序号

    SELECT CASE WHEN @mid = t.PAY_TIME THEN ELSE END SEQ, @mid := t.PAY_TIME, t.AMOUNTS, t.CHARGE_PRICE, ...

  2. pm2&period;5检测统计

    SELECT AVG(pm25_h),AVG(pm10_h),AVG(temp_h),AVG(humi_h),AVG(co2_h),AVG(tvoc_h),perf_time FROM( SELECT ...

  3. php的进制转换

    学习了php的进制转换,有很多的知识点,逻辑,也有最原始的笔算,但是我们还是习惯使用代码来实现进制的转换,进制的转换代码有如下:二进制(bin)八进制( oct)十进制( dec)十六进制( hex) ...

  4. maven工程导入时解决Cannot change version of project facet Dynamic Web Module to 2&period;3

    解决方法:修改web.xml,在头部加入内容,加入后为: <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&q ...

  5. Asp&period;net MVC 搭建属于自己的框架(一)

    为什么要自己搭框架? 大家伙别急,让我慢慢地告诉你!大家有没有这种感觉,从一家跳槽到另一家公司,公司的框架往往是不相同的,这样你必须就得摒弃原来的,学习新的框架. 问题是你用习惯了一种框架,比如封装的 ...

  6. 为了android sdk下载,必须修改hosts

    #Download 下载 203.208.46.146 dl.google.com 203.208.46.146 dl-ssl.google.com #Groups 203.208.46.146 gr ...

  7. Android studio 3&period;0 引起的 outputFile sync failed:not vaild

    我们大多使用 android studio 改变生成安装包命名会用以下方式: applicationVariants.all { variant -> variant.outputs.each ...

  8. POJ 1284:Primitive Roots(素数原根的个数)

    Primitive Roots Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 5709 Accepted: 3261 Descr ...

  9. ds18b20采集温度并上报服务器

    交叉编译器:arm-linux-gcc-4.5.4 Linux内核版本:Linux-3.0 主机操作系统:Centos 6.5 开发板:FL2440 温度传感器:ds18b20 注:此程序的客户端是在 ...

  10. 每日linux命令学习-grep模式检索

    grep模式检索指令包括grep,egrep,和fgrep,.Linux系统使用正则表达式优化文本检索,所以在此,笔者首先学习了一下正则表达式. 1. 正则表达式 正则表达式使用被称为元字符(Meta ...