一、前言
在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节。比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达式判断无意开发效率太慢,在时间、成本、质量的博弈中必然会落后。所以把校验层抽象出来是必然的结果,下面说下几种解决方案。
二、几种解决方案
1、struts2的valid可以通过配置xml,xml中描述规则和返回的信息,这种方式比较麻烦、开发效率低,不推荐
2、validation bean 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现.Hibernate Validator是一个hibernate独立的包,可以直接引用,他实现了validation bean同时有做了扩展,比较强大 ,实现图如下:
3、oval 是一个可扩展的Java对象数据验证框架,验证的规则可以通过配置文件、Annotation、POJOs 进行设定。可以使用纯 Java 语言、JavaScript 、Groovy 、BeanShell 等进行规则的编写,本次不过多讲解
三、基础校验类型
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, 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})
相关链接资料
Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC https://my.oschina.net/qjx1208/blog/200946
Java Bean Validation 最佳实践 http://www.cnblogs.com/beiyan/p/5946345.html