JAVA中的字段校验(validation)
作者:Saleson
在开发业务时,不可避免的需要处理一些校验, 如果是写if-else这种代码去校验, 那会有一大段这样的代码。
不过还好有个校验插件:javax.validation.validation-api,不过一般会引用hibernate的校验组件:org.hibernate.hibernate-validator, 它已经引用了validation-api组件。
基础校验类型
JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。
注解如下:
Hibernate validator 在JSR303的基础上对校验注解进行了扩展,扩展注解如下:
写个DEMO看看
校验工具类:ValidatorUtils
import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Created by saleson on 2017/10/13. */ public class ValidatorUtils { private static Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); public static <T> Map<String, String> validate(T obj) { Map<String, StringBuilder> errorMap = new HashMap<>(); Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class); if (set != null && set.size() > 0) { String property = null; for (ConstraintViolation<T> cv : set) { //这里循环获取错误信息,可以自定义格式 property = cv.getPropertyPath().toString(); if (errorMap.get(property) != null) { errorMap.get(property).append("," + cv.getMessage()); } else { StringBuilder sb = new StringBuilder(); sb.append(cv.getMessage()); errorMap.put(property, sb); } } } return errorMap.entrySet().stream().collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue().toString())); } }
DemoBean:
import com.fm.core.exceptions.ApiException; import org.hibernate.validator.constraints.NotEmpty; import com.fm.framework.api.ApiResultHelper; import com.fm.framework.json.Json; import com.fm.framework.utils.StringUtils; import com.fm.grantauth.domain.ValidateResult; import com.fm.grantauth.domain.dto.AuthorizationApplyDTO; import com.fm.grantauth.utils.ValidatorUtils; import org.junit.Test; import javax.validation.Valid; import java.util.Map; /** * Created by saleson on 2017/10/12. */ public class DataAuthValidator { private static final Logger log = LoggerFactory.getLogger(DataAuthValidator.class); @Override public ValidateResult validate(AuthorizationApplyDTO applyDTO) { String json = applyDTO.getContractParams(); if (StringUtils.isEmpty(json)) { throw new ApiException(ApiResultHelper.newParameterEmpty("contractParams字段不能为空")); } DataAuthContractParams params = Json.parseObject(json, DataAuthContractParams.class); Map<String, String> validMap = ValidatorUtils.validate(params); if (!validMap.isEmpty()) { log.warn(validMap.toString()); throw new ApiException(ApiResultHelper.newBusinessError(lackFieldMessage(validMap.keySet().toArray(new String[0])))); } return new ValidateResult(true); } public static class DataAuthContractParams { @NotEmpty private String businessName; @NotEmpty private String dataProvider; @NotEmpty private String personalDataName; @NotEmpty private String dataDemander; public String getBusinessName() { return businessName; } public void setBusinessName(String businessName) { this.businessName = businessName; } public String getDataProvider() { return dataProvider; } public void setDataProvider(String dataProvider) { this.dataProvider = dataProvider; } public String getPersonalDataName() { return personalDataName; } public void setPersonalDataName(String personalDataName) { this.personalDataName = personalDataName; } public String getDataDemander() { return dataDemander; } public void setDataDemander(String dataDemander) { this.dataDemander = dataDemander; } } @Test public void test() { AuthorizationApplyDTO applyDTO = new AuthorizationApplyDTO(); DataAuthContractParams params = new DataAuthContractParams(); params.setBusinessName("f"); params.setDataDemander("f"); params.setDataProvider(""); params.setPersonalDataName(""); applyDTO.setContractParams(Json.toJSONString(params)); ValidateResult result = new DataAuthValidator().validate(applyDTO); System.out.println(Json.toJSONString(result)); assert result.isSeccess(); } }
运行结果:
[main] WARN com.fm.grantauth.module.authorization.contact.DataAuthValidator - {dataProvider=不能为空, personalDataName=不能为空}
com.fm.core.exceptions.ApiException: 参数contractParams中缺少字段:dataProvider, personalDataName
自定义校验规则(Validator)
自定义注解:
package com.fm.core.validation; import com.fm.core.validation.validator.NotEmptyValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; /** * Created by saleson on 2017/5/31. */ @Retention(RetentionPolicy.RUNTIME) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Documented @Constraint(validatedBy = {NotEmptyValidator.class}) public @interface NotEmpty { String message() default "参数不能为null或空字符串"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
校验器(validator):
package com.fm.core.validation.validator; import com.fm.core.validation.NotEmpty; import com.fm.framework.utils.StringUtils; import org.apache.commons.collections.MapUtils; import javax.validation.ConstraintValidatorContext; import java.lang.reflect.Array; import java.util.Collection; import java.util.Map; /** * Created by saleson on 2017/5/27. */ public class NotEmptyValidator extends AbstractValidator<NotEmpty, Object> { @Override protected boolean validNull(ConstraintValidatorContext context) { return false; } @Override protected boolean valid(Object value, ConstraintValidatorContext context) { if (value instanceof String) { return StringUtils.isNotEmpty(value.toString()); } else if (value instanceof Collection) { return !org.springframework.util.CollectionUtils.isEmpty((Collection) value); } else if (value instanceof Map) { return MapUtils.isNotEmpty((Map) value); } else if (value.getClass().isArray()) { return Array.getLength(value) > 0; } return true; } }
将上个demo中引用的org.hibernate.validator.constraints.NotEmpty改为com.fm.core.validation.NotEmpty即可。
运行结果:
WARN com.fm.grantauth.module.authorization.contact.DataAuthValidator - {dataProvider=参数不能为null或空字符串, personalDataName=参数不能为null或空字符串}
com.fm.core.exceptions.ApiException: 参数contractParams中缺少字段:dataProvider, personalDataName
级联校验
校验的对象中包含另一个需要校验的对象时,则可以使用@javax.validation.Valid
import com.fm.core.exceptions.ApiException; import com.fm.core.validation.NotEmpty; import com.fm.framework.api.ApiResultHelper; import com.fm.framework.json.Json; import com.fm.framework.utils.StringUtils; import com.fm.grantauth.domain.ValidateResult; import com.fm.grantauth.domain.dto.AuthorizationApplyDTO; import com.fm.grantauth.utils.ValidatorUtils; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.Valid; import java.util.Map; /** * Created by saleson on 2017/10/12. */ public class DataAuthValidator implements ContractParamsValidator { private static final Logger log = LoggerFactory.getLogger(DataAuthValidator.class); @Override public ValidateResult validate(AuthorizationApplyDTO applyDTO) { String json = applyDTO.getContractParams(); if (StringUtils.isEmpty(json)) { throw new ApiException(ApiResultHelper.newParameterEmpty("contractParams字段不能为空")); } DataAuthContractParams params = Json.parseObject(json, DataAuthContractParams.class); Map<String, String> validMap = ValidatorUtils.validate(params); if (!validMap.isEmpty()) { log.warn(validMap.toString()); throw new ApiException(ApiResultHelper.newBusinessError(lackFieldMessage(validMap.keySet().toArray(new String[0])))); } return new ValidateResult(true); } public static class DataAuthContractParams { @NotEmpty private String businessName; @NotEmpty private String dataProvider; @NotEmpty private String personalDataName; @NotEmpty private String dataDemander; // @NotEmpty @Valid private Re r; public String getBusinessName() { return businessName; } public void setBusinessName(String businessName) { this.businessName = businessName; } public String getDataProvider() { return dataProvider; } public void setDataProvider(String dataProvider) { this.dataProvider = dataProvider; } public String getPersonalDataName() { return personalDataName; } public void setPersonalDataName(String personalDataName) { this.personalDataName = personalDataName; } public String getDataDemander() { return dataDemander; } public void setDataDemander(String dataDemander) { this.dataDemander = dataDemander; } public Re getR() { return r; } public void setR(Re r) { this.r = r; } } public static class Re { @NotEmpty private String d; public String getD() { return d; } public void setD(String d) { this.d = d; } } @Test public void test() { AuthorizationApplyDTO applyDTO = new AuthorizationApplyDTO(); DataAuthContractParams params = new DataAuthContractParams(); params.setBusinessName("f"); params.setDataDemander("f"); params.setDataProvider(""); params.setPersonalDataName(""); Re r = new Re(); params.setR(r); applyDTO.setContractParams(Json.toJSONString(params)); ValidateResult result = new DataAuthValidator().validate(applyDTO); System.out.println(Json.toJSONString(result)); assert result.isSeccess(); } }
运行结果:
WARN com.fm.grantauth.module.authorization.contact.DataAuthValidator - {r.d=参数不能为null或空字符串, dataProvider=参数不能为null或空字符串, personalDataName=参数不能为null或空字符串}
com.fm.core.exceptions.ApiException: 参数contractParams中缺少字段:r.d, dataProvider, personalDataName
分组校验
对同一个Model,我们在增加和修改时对参数的校验也是不一样的,这个时候我们就需要定义分组验证。
com.fm.core.validation.NotEmpty#groups()就是用于分组校验的
添加两个用于分组校验的接口:
public static interface GroupFirst { } public static interface GroupSecond { }
校验改成:
public static class DataAuthContractParams { @NotEmpty private String businessName; @NotEmpty private String dataProvider; @NotEmpty(groups = GroupFirst.class) private String personalDataName; @NotEmpty(groups = {GroupFirst.class, GroupSecond.class}) private String dataDemander; // @NotEmpty @Valid private Re r; public String getBusinessName() { return businessName; } public void setBusinessName(String businessName) { this.businessName = businessName; } public String getDataProvider() { return dataProvider; } public void setDataProvider(String dataProvider) { this.dataProvider = dataProvider; } public String getPersonalDataName() { return personalDataName; } public void setPersonalDataName(String personalDataName) { this.personalDataName = personalDataName; } public String getDataDemander() { return dataDemander; } public void setDataDemander(String dataDemander) { this.dataDemander = dataDemander; } public Re getR() { return r; } public void setR(Re r) { this.r = r; } } public static class Re { @NotEmpty(groups = GroupSecond.class) private String d; public String getD() { return d; } public void setD(String d) { this.d = d; } }
修改ValidatorUtils
package com.fm.grantauth.utils; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Created by saleson on 2017/10/13. */ public class ValidatorUtils { private static Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); public static <T> Map<String, String> validate(T obj) { Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class); return convertErrorMap(set); } public static <T> Map<String, String> validate(T obj, Class<?>... groups) { Set<ConstraintViolation<T>> set = validator.validate(obj, groups); return convertErrorMap(set); } private static <T> Map<String, String> convertErrorMap(Set<ConstraintViolation<T>> set) { Map<String, StringBuilder> errorMap = new HashMap<>(); if (set != null && set.size() > 0) { String property = null; for (ConstraintViolation<T> cv : set) { //这里循环获取错误信息,可以自定义格式 property = cv.getPropertyPath().toString(); if (errorMap.get(property) != null) { errorMap.get(property).append("," + cv.getMessage()); } else { StringBuilder sb = new StringBuilder(); sb.append(cv.getMessage()); errorMap.put(property, sb); } } } return errorMap.entrySet().stream().collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue().toString())); } }
测试GroupFirst.class:
@Test public void test() { DataAuthContractParams params = new DataAuthContractParams(); params.setBusinessName("f"); params.setDataDemander(""); params.setDataProvider("f"); params.setPersonalDataName(""); Re r = new Re(); params.setR(r); Map<String, String> validMap = ValidatorUtils.validate(params, GroupFirst.class); System.out.println("error is: " + validMap); }
运行结果:
error is: {dataDemander=参数不能为null或空字符串, personalDataName=参数不能为null或空字符串}
测试GroupSecond.class:
@Test public void test() { DataAuthContractParams params = new DataAuthContractParams(); params.setBusinessName("f"); params.setDataDemander(""); params.setDataProvider("f"); params.setPersonalDataName(""); Re r = new Re(); params.setR(r); Map<String, String> validMap = ValidatorUtils.validate(params, GroupSecond.class); System.out.println("error is: " + validMap); }
运行结果:
error is: {dataDemander=参数不能为null或空字符串, r.d=参数不能为null或空字符串}
测试GroupFirst.class+GroupSecond.class:
@Test public void test() { DataAuthContractParams params = new DataAuthContractParams(); params.setBusinessName("f"); params.setDataDemander(""); params.setDataProvider("f"); params.setPersonalDataName(""); Re r = new Re(); params.setR(r); Map<String, String> validMap = ValidatorUtils.validate(params, GroupFirst.class, GroupSecond.class); System.out.println("error is: " + validMap); }
运行结果:
error is: {dataDemander=参数不能为null或空字符串, r.d=参数不能为null或空字符串, personalDataName=参数不能为null或空字符串}
spring mvc上使用分组校验:
@org.springframework.validation.annotation.Validated({GroupFirst.class, GroupSecond.class})
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。