前言
Spring Validation
作为一个参数验证框架,本身提供的注解已经很强大了,能覆盖大部分业务场景需求,比如:@NotNull
, @NotBlank
, @Min
, @Max
, @Size
, @Email
等等。
但是对于更加复杂的业务场景,Spring Validation
自带的这些注解就无能为力了,比如,假设有个接口,其中的一个入参是 String
类型,由于业务需要,该入参值的范围只能在一个允许的 list
中,不在范围中的传入值,直接返回异常。
常规做法是在业务代码里对入参做一层逻辑判断,但是这样做会导致业务代码和校验代码耦合,且开发效率不高,代码十分不美观。 更加优雅的做法是自定义一个 validator
配合枚举类,来实现这个需求。
架构图
模块说明:
1: DTO
- 接收入参的DTO
类,使得收到的参数变成一个对象
1-1: private String para
对象加了 @EnumCheck
注解,表示 para
需要被校验
1-2: private String para1
对象未加 @EnumCheck
注解,表示 para1
不需要被校验
2: EnumCheck
- 是一个注解类,需要在该类里实现自定义注解
3: EnumUtil
- 枚举工具类,通过传入一个枚举参数,判断该参数是否在指定的枚举里,存在则返回枚举,不存在返回null
4: Enum Class
- 用户定义的枚举类,用于存放入参的范围
5: 异常逻辑 - 当EnumCheck
失败后,进入异常逻辑
6: 后续业务逻辑 - 当EnumCheck
成功后,进入后续的业务逻辑
代码说明
JenkinsProcessBuildReqDTO.java
入参用该DTO
类接收,在该类中定义需要被自定义validate
的一个或者多个字段。
@DatapublicclassJenkinsProcessBuildReqDTO{/***部署环境*/@EnumCheck(message="environment输入有误",enumClass=JenkinsProcessEnum.class)privateStringenvironment;}
JenkinsProcessEnum.java
用户自定义的枚举类,用于存放入参的范围,如该枚举类中,限定了 JenkinsProcessBuildReqDTO.environment
的范围在 DEPLOY_SIT
, DEPLOY_UAT
, DEPLOY_SIT_UAT
这三个枚举里。
importlombok.Getter;@GetterpublicenumJenkinsProcessEnum{/***sit环境*/DEPLOY_SIT("0","SIT","sit环境"),/***uat环境*/DEPLOY_UAT("1","UAT","uat环境"),/***sit和uat环境*/DEPLOY_SIT_UAT("2","SIT_UAT","sit和uat环境"),;privateStringcode;privateStringname;privateStringdesc;JenkinsProcessEnum(Stringcode,Stringname,Stringdesc){this.code=code;this.name=name;this.desc=desc;}}
EnumUtil.java
该类中提供了一个方法getEnumByParameter
: 通过传入一个枚举参数 enumParameter
,判断该参数是否在指定的枚举 clazz
里。存在则返回 enumParameter
对应的枚举,不存在返回 null
@Slf4jpublicclassEnumUtil{/****@authorarkMon*@date14:222021/2/23*@paramclazz传入的枚举类名*@paramgetEnumMethodName传入的枚举类clazz中的方法名称*@paramenumParameter枚举参数*@returnT具体的枚举值或者null*/publicstatic<TextendsEnum<T>>TgetEnumByParameter(Class<T>clazz,StringgetEnumMethodName,ObjectenumParameter){Tresult=null;try{//Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例T[]arr=clazz.getEnumConstants();//获取定义的方法MethodtargetMethod=clazz.getDeclaredMethod(getEnumMethodName);if(targetMethod==null){log.error("getEnumMethodName="+getEnumMethodName+"不存在");returnnull;}ObjecttypeVal;//遍历枚举定义for(Tentity:arr){if(enumParameterinstanceofInteger){typeVal=Integer.valueOf(String.valueOf(targetMethod.invoke(entity)));}elseif(enumParameterinstanceofString){//获取传过来方法的typeVal=String.valueOf(targetMethod.invoke(entity)).replace("","");//执行的方法的值等于参数传过来要判断的值enumParameter=((String)enumParameter).replace("","");}else{log.error("传入的enumType不是Integer也不是String类型");returnnull;}if(typeVal.equals(enumParameter)){//返回这个枚举result=entity;break;}}}catch(IllegalAccessException|IllegalArgumentException|InvocationTargetException|NoSuchMethodException|SecurityExceptione){e.printStackTrace();}returnresult;}}
EnumCheck.java
自定义验证器的注解。在该注解下的validator
类中的isValid
方法里,实现自定义验证器的逻辑:
判断传入的参数是否可以通过枚举里的code
或者name
,用getEnumByParameter
方法找到其对应的枚举,能找到返回true
,不能找到返回false
。
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy=EnumCheck.Validator.class)public@interfaceEnumCheck{Stringmessage()default"{enum.value.invalid}";//错误信息Class<?extendsEnum<?>>enumClass();//枚举类Class<?>[]groups()default{};Class<?extendsPayload>[]payload()default{};StringenumMethodCode()default"getCode";//枚举校验方法StringenumMethodName()default"getName";//枚举校验方法booleanallowNull()defaultfalse;//是否允许为空classValidatorimplementsConstraintValidator<EnumCheck,Object>{privateClass<?extendsEnum<?>>enumClass;privateStringenumMethodCode;privateStringenumMethodName;privatebooleanallowNull;@Overridepublicvoidinitialize(EnumCheckenumValue){enumMethodCode=enumValue.enumMethodCode();enumMethodName=enumValue.enumMethodName();enumClass=enumValue.enumClass();allowNull=enumValue.allowNull();}@OverridepublicbooleanisValid(Objectvalue,ConstraintValidatorContextconstraintValidatorContext){if(value==null){returnallowNull;}if(enumClass==null){returnBoolean.TRUE;}JenkinsProcessEnumenumByParameter=EnumUtil.getEnumByParameter(JenkinsProcessEnum.class,enumMethodCode,value);if(enumByParameter!=null){returntrue;}JenkinsProcessEnumenumByParameter1=EnumUtil.getEnumByParameter(JenkinsProcessEnum.class,enumMethodName,value);if(enumByParameter1!=null){returntrue;}returnfalse;}}}
Ref
使用spring validation完成数据后端校验
Java Bean Validation 最佳实践
作者:arkMon