一、环境与业务背景
1、开发环境
jdk1.8、maven
maven依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
2、业务背景
因作者所在公司,代码规范,规定入参与出参字段类型都为String,针对某些特定入参取值内容的校验就很有必要,本文针对常见的两种取值内容格式进行校验,进行了自定义业务注解。第一种特定的单个取值内容格式,另外一种以拼接内容的格式进行取值,校验这两种类型取值格式的方式有多种实现方案,本文提供的为常见实现方式。
二、入参取值类型
1、单值取值格式
定义:入参字段的取值为0或1、Y或N等这种格式内容。
以下案例以0或1这个格式举例:
1.1、自定义业务参数校验注解
import com.demo.validation.rule.AbstractStatusValueHandlerRule;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
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;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(StatusValue.List.class)
@Documented
@Constraint(validatedBy = {StatusValueValidated.class})
public @interface StatusValue {
/**
* 是否需要为空(true:为空,false:不为为空)
*/
boolean isNull() default false;
Class<? extends AbstractStatusValueHandlerRule> handlerRule() default AbstractStatusValueHandlerRule.class;
String message() default "{javax.validation.constraints.StatusValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
StatusValue[] value();
}
}
1.2、自定义实现校验规则类
规则接口:
public interface StatusValueHandlerRule<T> {
/**
* 是否有效
* @param value T
* @return true-有效;false-无效
*/
boolean isValidation(T value);
}
抽象类:
public abstract class AbstractStatusValueHandlerRule<T> implements StatusValueHandlerRule<T> {
}
String入参类型取值校验规则类:
public class StringStatusValueRule extends AbstractStatusValueHandlerRule<String> {
@Override
public boolean isValidation(String value) {
Optional<CommentYesOrNoEnum> first = Stream.of(CommentYesOrNoEnum.values())
.filter(enums -> Objects.equals(enums.getCode(), value))
.findFirst();
return first.isPresent();
}
}
枚举类:
@Getter
public enum CommentYesOrNoEnum {
YES("1","是"),
NO("0","不"),
;
private String code;
private String msg;
CommentYesOrNoEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
1.3、实现该注解的校验逻辑
import com.demo.validation.rule.AbstractStatusValueHandlerRule;
import com.demo.validation.rule.StatusValueHandlerRule;
import lombok.SneakyThrows;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Objects;
public class StringStatusValueValidated implements ConstraintValidator<StatusValue,String> {
private StatusValueHandlerRule statusValueRule;
private boolean isNull;
@SneakyThrows
@Override
public void initialize(StatusValue constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
Class<? extends AbstractStatusValueHandlerRule> handlerRule = constraintAnnotation.handlerRule();
statusValueRule = handlerRule.newInstance();
isNull = constraintAnnotation.isNull();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (isNull && Objects.isNull(value)) {
return true;
}
if (Objects.isNull(value)){
return false;
}
return statusValueRule.isValidation(value);
}
}
2、多值以拼接方式取值格式
例如:入参取值格式内容为:1-2-3-4-5。
2.1、自定义注解
import cn.hutool.core.util.StrUtil;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
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;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(MontageStrValue.List.class)
@Documented
@Constraint(validatedBy = {MontageStrValueValidated.class})
public @interface MontageStrValue {
/**
*
* 是否需要为空(true:为空,false:不为为空)
*/
boolean isBlank() default false;
/**
* 分割之后数组元素最大个数
*/
int maxSize() default 0;
/**
* 分割符
*/
String separator() default StrUtil.COMMA;
String message() default "{javax.validation.constraints.MontageStrValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
MontageStrValue[] value();
}
}
2.2、实现该注解的校验逻辑
public class MontageStrValueValidated implements ConstraintValidator<MontageStrValue,String> {
/**
* 数组最大个数
*/
private int maxSize;
/**
* 是否为空
*/
private boolean isBlank;
/**
* 分割符
*/
private String separator;
@Override
public void initialize(MontageStrValue constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
maxSize = constraintAnnotation.maxSize();
isBlank = constraintAnnotation.isBlank();
separator = constraintAnnotation.separator();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (isBlank && StrUtil.isBlank(value)) {
return true;
}
if (StrUtil.isBlank(value)) {
return false;
}
String[] split = value.split(separator);
return split.length <= maxSize;
}
}
三、总结
使用JSR303注解要实现个性化入参校验,只需要做到两步即可,第一步:自定义一个注解。第二步:实现该注解的校验逻辑。
第一步:自定义一个注解
自定义注解时重点注意以下注解:@Constraint(validatedBy = {}),这个注解很关键,里面的validatedBy = {}是可以指定具体的校验类,例如:validatedBy = {MontageStrValueValidated.class},具体的校验逻辑在MontageStrValueValidated类里面实现。另外就是注解里面的一些属性,例如message、groups、payload和内部的一个@List注解,这里可以参考validation已有的注解,基本都是很有用的,然后就是定义自己需要的一些特殊的属性,方便校验,例如MontageStrValue注解中就包含了isBlank、maxSize、separator。
第二步:实现该注解的校验逻辑
例如: MontageStrValueValidated implements ConstraintValidator<MontageStrValue,String> 实现两个方法,分别是initialize(初始化方法)和isValid(校验方法),initialize()主要是加载读取注解上的值并赋值给类变量,isValid()是实现具体的校验逻辑,此处可自行实现具体的校验逻辑。