通过Spring注解校验枚举参数的合法性

概要

在我们使用Spring mvc的开发Restful风格的接口时,往往需要后端对请求参数进行合法性校验,并且返回包含异常提示信息给到用户提示或接口提示,以满足我们接口的合法性,防止后续的请求业务逻辑出现不可控的异常。

Spring Validated注解作用

在一个正常的POST带body请求体的请求接口当中,@Validated注解可用用来校验请求参数的合法性。@Valid也可以做参数校验,但题主使用了自定义分组校验,所以未使用@Valid注解。

关于@Validated和@Valid注解的区别,请参考这边文章:@Validated和@Valid区别

定义一个通用的枚举校验器

  1. 首先定义一个枚举校验器,题主这边的枚举校验器只能校验,String类型和Integer类型。如果需要支持其他的类型,请各位coder自行实现。
/**
 * 枚举校验注解处理类
 */
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {

    private Class<? extends Enum> enumClass;

    @Override
    public void initialize(EnumValue constraintAnnotation) {
        enumClass = constraintAnnotation.enumClass();
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (!(value instanceof Integer || value instanceof String)) {
            return false;
        }
        List<String> strEnumCode;
        List<Integer> intEnumCode;
        Method enumValidatorList;
        try {
            enumValidatorList = enumClass.getMethod("enumValidatorList");
        } catch (NoSuchMethodException e) {
            return false;
        }
        Object[] objects = enumClass.getEnumConstants();
        List<?> result = Lists.newArrayList();
        try {
            result = (List<?>) enumValidatorList.invoke(objects[0]);
        } catch (IllegalAccessException | InvocationTargetException e) {
            return false;
        }
        if (CollectionUtils.isEmpty(result)) {
            return false;
        }
        //可以校验的数据类型实现。暂时我这边的业务只需要支持Integer和String。
        //对于大部分的业务场景应该都是满足的。
        Object firstElement = result.get(0);
        if (firstElement instanceof String) {
            strEnumCode = (List<String>) result;
            return strEnumCode.contains((String) value);
        } else if (firstElement instanceof Integer) {
            intEnumCode = (List<Integer>) result;
            return intEnumCode.contains((Integer) value);
        } else {
            return false;
        }
    }
}

  1. 定义一个通用的枚举校验注解。
/**
 * 枚举校验注解,只能校验为Integer或者String类型的参数
 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {

    // 默认错误消息
    String message() default "必须为指定类型";

    //校验的枚举类
    Class<? extends Enum> enumClass();

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};

    // 指定多个时使用
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        EnumValue[] value();
    }
}

  1. 定义一个供所有的枚举继承的接口,需要将枚举的定义的数组值赋值到EnumValue
    这个累的 List属性当中。
/**
 * 公共类,用来做枚举类型校验的基类
 * 暂时只支持Integer类型和Sting类型的参数
 */
public interface Enum<T> {
    List<T> enumValidatorList();
}

有了上面的这些基础代码,我们就可以实现对枚举类型数据的合法性校验了。

具体的使用方法

  • 定义一个枚举类型数据,我们这里拿可用状态来举个例子。
@Getter
public enum EnableStatusEnum implements Enum<Integer> {

    //正常
    NORMAL(1, "启用"),
    //禁用
    DISABLE(0, "禁用");

    /**
     * 状态code
     */
    private final Integer status;

    /**
     * 状态描述
     */
    private final String statusName;

    EnableStatusEnum(Integer status, String statusName) {
        this.status = status;
        this.statusName = statusName;
    }

    @Override
    public List<Integer> enumValidatorList() {
        List<Integer> codes = Lists.newArrayList();
        for (EnableStatusEnum value : EnableStatusEnum.values()) {
            codes.add(value.getStatus());
        }
        return codes;
    }
}
  • 定义一个请求参数对象reqDto或者数据库生成的entity对象,此处我们假设对新增/修改一个用户举例,定义一个用户对象信息。
@ApiModel(value = "用户信息")
@Data
@Accessors(chain = true)
public class User implements Serializable {

    @ApiModelProperty(value = "")
    @NotNull(groups = {UpdateGroup.class}, message = "修改时d不能为空")
    @Null(groups = {InsertGroup.class}, message = "新增时id不能存在")
    private Long id;

	
    @ApiModelProperty(value = "状态:-1 删除 0 禁用 1 启用")
    @NotNull(groups = {UpdateGroup.class, InsertGroup.class}, message = "用户状态不能为空")
    @EnumValue(enumClass = EnableStatusEnum.class, message = "用户状态不合法")
    @LogTag(alias = "是否启用")
    private Integer status;
}
  • 接口当中去新增或修改这个User对象,这个时候当用户的状态不满足我们定义的枚举当中的状态是,spring就会报一个MethodArgumentNotValidException 参数校验异常的全局错误,我们将指定类型的异常通过全局异常处理器捕获处理就行了。
    @ApiOperation(value = "新增用户")
    @RequestMapping(value = "/grade", method = RequestMethod.POST)
    @ResponseBody
    public OperationResultDto insertUser(@RequestBody @Validated 
    User user) {
        return userService.insert(user);
    }

    /**
     * 使用@Valid或@Validated注解时,如果校验失败,会抛出
     * MethodArgumentNotValidException异常,这里捕获该异常,返回校验失败的信息
     * 此时需要注意,不需要在校验的注解后面跟BindingResult参数,否则不会报异常
     *
     * @param ex MethodArgumentNotValidException 异常
     * @return API端对该参数异常的处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Object handleValidationException(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        // 处理校验失败的情况,返回错误信息等
        logger.error(ex.getMessage(), ex);
        return packMessage(OperationResDto.fail(result.getAllErrors().get(0).getDefaultMessage()), OperationStatusEnum.CUSTOMIZED_ERROR.value());
    }

小结

通过上述的处理方式,我们就可以全局的对枚举类型的数据进行通用的有效校验,并且给出用户合理的提示和反馈了。当后台枚举类型新增也不会影响到整个代码的修改,当某个环节忘记处理新增的枚举类型时,整个系统也会给个友好提示,而不是抛出异常,保证了代码的可靠性。其中代码还是有优化的地方的,比如说如何在定义枚举时,不去继承自身定义的枚举类而将属性列表数据能写到注解方法上,大家可以自行优化这部分逻辑。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值