1. 导读
在springboot 中,校验对象的参数值时,如果某个参数的值是固定的几个,那么可以使下面的枚举校验器 或 固定值校验证器。这两种验证器目录适用于参数值是数字、字符串,也可以进行拓展。下面举例介绍的 自定义枚举校验 和 固定值校验证的适用场景基本一样。
2. 业务说明
2.1 业务场景一
邮寄状态 postStatus 的取值限定在范围是 0、1、2、3 。其中:
- 0 :表示 未邮寄
- 1 :表示 已邮寄
- 2 :表示 收到货
- 3 :表示 邮寄出错
2.2 业务场景二
证件类型 cardType 的取值 限定范围是 A、B、C、D、E、F 。其中:
- A :表示 身份证
- B :表示 护照
- C :表示 军官
- D :表示 台湾居民来往大陆通行证
- E :表示 港澳居民来往内地通行证
- F :表示 外国人永久居留
2.3 解决思路
有两种方式可以实现:
- 使用validator的@Pattern注解
业务场景一此时需要将postStatus的类型改为String类型
@Pattern(regexp = "^[0-3]$", message = "post status must be 0、1、2、3")
业务场景二
@Pattern(regexp = "^[A-F]$", message = "card type must be A、B、C、D、E、F")
- 使用自定义枚举校验器
3. 使用自定义枚举校验器
3.1 定义通用的枚举校验器 EnumValueValidator
这个枚举校验器是通用的,正常情况下,一个项目中只要写这一枚举校验器即可。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.Validator.class)
/**
* 枚举校验器
*/
public @interface EnumValidator {
String message() default "{ EnumValidator's value is invalid }";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends EnumValidations.Enumerable> enumClass();
class Validator implements ConstraintValidator<EnumValidator, Object> {
private Class<? extends EnumValidations.Enumerable> enumClass=null;
@Override
public void initialize(EnumValidator enumValue) {
enumClass = enumValue.enumClass();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return Boolean.FALSE;
}
try {
EnumValidations.Enumerable result = off(enumClass,String.valueOf(value));
return result == null ? false : true;
} catch (Exception e) {
e.printStackTrace();
return Boolean.FALSE;
}
}
public static <E extends EnumValidations.Enumerable> E off(@Nonnull Class<E> classType, String value) {
for (E enumConstant : classType.getEnumConstants()) {
if (enumConstant.getCode().equals(value)) {
return enumConstant;
}
}
return null;
}
}
}
3.2 定义枚举值
定义枚举类:
// 所有的枚举校验都在此文件中
public class EnumValidations {
public interface Enumerable{
public String getCode();
public String getText() ;
}
/**
* 邮寄状态
*/
public enum PostStatusEnum implements Enumerable {
//0:未邮寄, 1:已邮寄,2:收到货,3:邮寄出错
A("0", "未邮寄"),
B("1", "已邮寄"),
C("2", "收到货"),
D("3", "邮寄出错");
private String code;
private String desc;
private PostStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
/**
*用户信息 —— 证件类型 CardType
*/
public enum UserInfoCardTypeEnum implements Enumerable {
A("A", "身份证"),
B("B", "护照"),
C("C", "军官"),
D("D", "台湾居民来往大陆通行证"),
E("E", "港澳居民来往内地通行证"),
F("F", "外国人永久居留") ;
private String code;
private String desc;
private UserInfoCardTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
}
3.3 使用校验器
public class UpdateOrderStatusVO implements Serializable {
/**
* 邮寄状态:0:未邮寄, 1:已邮寄,2:收到货,3:邮寄出错
*/
@NotNull(message="邮寄状态不能为空")
@EnumValidator(enumClass = EnumValidations.PostStatusEnum.class,message="邮寄状态取值不正确")
private String postStatus;
/**证件类型 */
@NotNull(message="证件类型不能为空")
@EnumValidator(enumClass = EnumValidations.UserInfoCardTypeEnum.class,message="证件类型取值不正确")
private String cardType;
// getter/setter
}
使用枚举校验会出现大量的重复代码,比如 属性、构造函数、 getter/setter 等。下面推荐一种更优化方法。
4. 使用 固定值校验证器(推荐)
4.1 定义 固定值校验证器 FixedValueValidator
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {FixedValueValidator.FixedValueValid.class})
public @interface FixedValueValidator {
String message() default "FixedValue's value is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fixedValue();
class FixedValueValid implements ConstraintValidator<FixedValueValidator, Object> {
String[] fixedValue = null;
public void initialize(FixedValueValidator validData) {
this.fixedValue = validData.fixedValue();
}
public boolean isValid(Object value, ConstraintValidatorContext constraintContext) {
if (fixedValue == null || fixedValue.length == 0 ) {
return false;
}
if (value == null) {
return true;
}
boolean flag = false;
for (String str : fixedValue) {
if (String.valueOf(value).equals(String.valueOf(str))) {
flag = true;
break;
}
}
return flag;
}
}
}
4.2 使用 固定值校验证
public class UpdateOrderStatusVO implements Serializable {
/** 邮寄状态 */
@NotNull(message="邮寄状态不能为空")
@FixedValueValidator(fixedValue ={"0", "1", "2", "3" }, message="邮寄状态值错误" )
private String postStatus;
/** 证件类型 */
@NotNull(message="证件类型不能为空")
@FixedValueValidator(fixedValue ={"A", "B", "C", "D", "E", "F"}, message="证件类型值错误")
private String cardType;
// getter/setter
}
5. 捕获错误
方法1:通过 BindingResult 捕获错误
@RequestMapping("/order/updateStatus")
public void save(@Validated UpdateOrderStatusVO vo, BindingResult bindingResult,@Validated Student student, BindingResult bindingResult2) {
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
if (bindingResult2.hasErrors()) {
for (ObjectError error : bindingResult2.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
方法2:通过 ConstraintViolationException 捕获错误(推荐)
@RequestMapping("/order/updateStatus")
public void save(@Validated UpdateOrderStatusVO vo,@Validated Student student) {
// 。。。。
}
统一的异常的捕获类:
@ControllerAdvice
@Component
public class GlobalExceptionHandler {
/**
* 拦截捕捉 MissingServletRequestParameterException 异常
*/
@ResponseBody
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public String doMissingServletRequestParameterHandler(MissingServletRequestParameterException exception) {
MissingServletRequestParameterException exs = (MissingServletRequestParameterException) exception;
System.err.println(exs.getMessage());
return "bad request, ";
}
/**
* 拦截捕捉 ConstraintViolationException 异常
*/
@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public Map ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
Map map = new HashMap();
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
List<String> msgList = new ArrayList<>();
while (iterator.hasNext()) {
ConstraintViolation<?> cvl = iterator.next();
System.err.println(cvl.getMessageTemplate());
msgList.add(cvl.getMessageTemplate());
}
map.put("status", 500);
map.put("msg", msgList);
return map;
}
}
6. 总结
通过对两个校验器的使用、比较,发现 固定值校验器 使用更加的简单,不需要创建类、属性、构造方法和 getter/setter 等。