Hi,大家好,在我们Java猿进行项目开发的时候,写接口是我们最常做的事情,接口的参数校验,这个是一个很不起眼,但是做的不好,代码会非常冗余难看的事情,今天给大家分享一个框架,其实大家很常见的框架:hibernate-validator,接下来说说我在想项目里如何使用的。
首先引入依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.15.Final</version>
</dependency>
接下来我要定义一个注解,主要是修饰需要做参数验证的方法:
/**
* 参数验证
*
* @author Administrator
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParameterValidation {
}
定义一个切面用来处理这个注解修饰的方法,代码如下:
/**
* 拦截并打印Controller日志
*
* @author chenhj_sh
*/
@Slf4j
@Aspect
@Order(value = 12)
public class ParameterValidationAspect {
private Validator validator;
/**
* 切入点
*/
@Pointcut(value = "@annotation(com.billion.common.annotation.ParameterValidation)")
public void pointCut() {
// point cut
}
public ParameterValidationAspect(Validator validator) {
this.validator = validator;
}
/**
* 环绕通知
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
for (Object arg : args) {
if (arg != null) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(arg);
if (!CollectionUtils.isEmpty(constraintViolations)) {
for (ConstraintViolation constraintViolation : constraintViolations) {
throw new PayException(ErrorCode.INVALID_ARGUMENT.getCode(), constraintViolation.getMessageTemplate());
}
}
}
}
}
return joinPoint.proceed();
}
}
这里如果校验不通过,我们可以做自己的判断,我这里是抛出自定义的异常,然后再统一异常里处理,返回自定义格式,同学们可以按照自己的处理方案处理。
接着在使用的时候
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory();
return validatorFactory.getValidator();
}
@Bean
public ParameterValidationAspect parameterValidationAspect(Validator validator) {
return new ParameterValidationAspect(validator);
}
}
在我们需要做参数校验的方法上加注解
/**
* 分页查询角色
*
* @param queryParam
* @return
*/
@Override
@ParameterValidation
public PageResult<RoleAdminDto> getRoleByPage(QueryParam<RoleQueryCondition> queryParam) {
PageResult<Role> pageResult = roleProvider.findByPage(queryParam.getPageArg(), queryParam.getSortArgs(), queryParam.getCondition());
return new PageResult<>(pageResult.getPageInfo(), roleConverter.convertRoleList2DtoList(pageResult.getResult()));
}
接下来说下各个校验规则:
例如:
/**
* 查询数据的相关参数
*
* @author chenhuaijie
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(value = "查询参数")
public class QueryParam<C extends QueryCondition> {
@NotNull(message = "Parameter [condition] can not be null.")
@ApiModelProperty(value = "查询条件", position = 1)
private C condition;
@Valid
@NotNull(message = "Parameter [pageArg] can not be null.")
@ApiModelProperty(value = "分页参数", position = 2)
private PageArg pageArg;
@Valid
@ApiModelProperty(value = "排序参数", position = 3)
private List<SortArg> sortArgs;
}
@NotNull都是内置的常用的校验注解
注意,如果包含的是对象,且对象里的也需要校验,就要用@Valid修饰,嵌套对象里的注解写法与外面一致
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(value = "分页参数")
public class PageArg {
@NotNull(message = "Parameter [pageSize] can not be null.")
@Min(value = 1, message = "Parameter [pageSize] can not less than 1")
@Max(value = 10000, message = "Parameter [pageSize] can not grater than 10000")
@ApiModelProperty(value = "每页记录数(大于等于1,小于等于10000)", position = 1, example = "10", required = true)
private Integer pageSize;
@NotNull(message = "Parameter [pageNum] can not be null.")
@Min(value = 1, message = "Parameter [pageNum] can not less than 1")
@ApiModelProperty(value = "页码(从1开始编号)", position = 2, example = "1", required = true)
private Integer pageNum;
}
这样我们就可以做参数校验了,我测试了一下:
这个框架本身自带了很多校验注解,在多数时候都是满足我们需求的:
但是框架提给我们提供了拓展的能力,我们可以自定义注解来满足我们的要求。
例如我要实现一个验证,就是给定一个值,这个值得有效值必须是指定枚举的有效值,这个能够理解吧,如果我传入了一个性别的参数,但是性别枚举只有两种值,我要校验这个值是否有效。
定义枚举:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidEnumsValidator.class)
public @interface ValidEnum {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class enumClass();
String fieldName() default "value";
}
校验逻辑实现:
@Slf4j
public class ValidEnumsValidator implements ConstraintValidator<ValidEnum, Object> {
private Class enumClass;
private String fieldName;
@Override
public void initialize(ValidEnum constraintAnnotation) {
enumClass = constraintAnnotation.enumClass();
fieldName = constraintAnnotation.fieldName();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
try {
Object[] objs = enumClass.getEnumConstants();
if (objs == null || objs.length == 0) {
return false;
}
String methodName = methodName(fieldName);
Method method = enumClass.getMethod(methodName);
return Arrays.stream(objs).anyMatch(x -> {
try {
Object object = method.invoke(x);
return object.equals(value);
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
});
} catch (NoSuchMethodException e) {
log.error(e.getMessage(), e);
return false;
}
}
private String methodName(String filedName) {
return "getUsername" + String.valueOf(filedName.charAt(0)).toUpperCase() + filedName.substring(1);
}
}
使用的时候这么用:
@NotBlank(message = "Parameter [order] can not be null.")
@ValidEnum(enumClass = Order.class, message = "Parameter [order] is invalid,required 'asc' or 'desc'")
@ApiModelProperty(value = "排序方式:降序-\"desc\";升序-\"asc\"(不区分大小写)", position = 2)
private String order;
总结:
参数验证这个很不起眼的功能,不做的话,会导致很多垃圾数据进入我们的系统,轻则报一些很低级的错误,严重的脏数据进入了数据库可能会产生难以估量的风险。
如何做到很优雅的校验参数,上面是我的工作总结,希望可以帮到大家。