最近在项目中遇到了一个小小的问题,和大家分享一下,简单的接口但是在不同的业务场景下需要有不同的校验逻辑,有的参数在特定的场景下需要校验,有的参数在另外的场景下则不需要校验。解决方案有很多种加上我当时是刚刚入职为了偷懒贪图省事,所以就写了一大堆的if/else。如下展示(由于业务原因,敏感字段已转换):
代码语言:javascript复制public void checkParams(DTO dto) {
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
if (ObjectUtils.isEmpty(dto.getParam())) {
throw new ServiceException("Please fill in the param number");
}
..............
}
为了方法的可读性吧,我还特意将检验参数的方法抽离出checkParams单独的方法。当时想这样应该也就可以了吧。万万没想到啊,我在编写完代码的时候在pull代码前的编译的时候,PMD
检查没有过这个是把我给恶心到了当时报了一个错叫做什么 Avoid really long methods 和 GodClass这就让我很尴尬了,临门一脚给我拦住了很是郁闷。当时想我一个新来的需要抓紧把代码提上去不能拖延时间啊,接着我又开始了一系列的骚操作是你们不可能想到的。如下:
相比各位童鞋们看到参数的命名很无语吧,当时我也很无语其实我下意识是不想这么做的。但是没有办法,当时着急熟悉代码。以及完成工作没办法我还是硬着头皮把这个代码给改掉了。想着以后有时间改掉吧。天不遂人愿啊真的是。隔天我就经历了code review,各位你们可知道那种场合,我写的代码被斩首示众,我当时简直脚趾抠出来三室一厅的尴尬,好在是我leader明白我当时的困境理解我(此时我还是很庆幸的),说没事有时间改过来就好。cw一完事,我忙完自己手里的活,果断的偷偷的把这个低级错误给办了。利用了自定义注解的方式。废话不多说给大家看一下核心代码。
- 首先我建立了一个
注解类
package com.mb.rks.cases.common.annotation;
import java.lang.annotation.*;
/**
* @author
* <p>自定义注解校验参数<p>
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MetadataValidation {
/**
* 错误信息
* @return
*/
String message() default "参数不能为空";
/**
* 正则表达式
* @return
*/
String pattern() default "";
}
- 然后利用反射写了一个
验证器
package com.mb.rks.cases.common.utils;
import com.mb.rks.cases.common.annotation.MetadataValidation;
import com.mb.rks.cases.common.exception.TipsValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author
* <p>Validation验证器<p>
* <p>
* 对有自定义注解@Validation的参数进行校验
*/
@Slf4j
public final class ValidationCheckUtils {
/**
* 因为是工具类,所以不可以通过new的方式去创建,顾将其构造方法写成如下方式
*/
protected ValidationCheckUtils(){}
/**
* 检验方法:
* <p>扫描对象的属性,查看是否有@Validation注解,有注解的进行校验
*
* @param o 要校验的对象,入参对象
*/
public static void check(Object o) {
if (null == o) {
return;
}
Class<?> clazz = o.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
// 对象的所有属性
fieldList.forEach(field -> {
ReflectionUtils.makeAccessible(field);
try {
// 获取属性值
MetadataValidation annotation = field.getAnnotation(MetadataValidation.class);
// 没有注解则不做处理
if (null == annotation) {
return;
}
// 获取指定对象o上此 Field 表示的字段的值
Object value = field.get(o);
checkNotNull(value, annotation);
checkPattern(value, annotation);
} catch (IllegalAccessException e) {
log.error("Validation验证起数据解析失败:{}", e.getMessage());
}
});
}
/**
* 非空判断
*
* @param value 属性值
* @param annotation 注解信息
*/
private static void checkNotNull(Object value, MetadataValidation annotation) {
log.info("开始非空校验");
if (annotation != null && ObjectUtils.isEmpty(value)) {
throw new RuntimeException(annotation.message());
}
}
/**
* 正则校验
*
* @param value 属性值
* @param annotation 注解信息
*/
private static void checkPattern(Object value, MetadataValidation annotation) {
// 存在正则表达式
if (null != annotation.pattern() && annotation.pattern().length() > 0) {
Pattern pattern = Pattern.compile(annotation.pattern());
// 以pattern的规则匹配value的值
Matcher matcher = pattern.matcher(value.toString());
// 属性值不符合正则表达式所制定的格式,抛出异常
if (!matcher.matches()) {
throw new RuntimeException(annotation.message());
}
}
}
}
- 在需要调用校验参数的地方可以加上这样一串代码
// 点击发送之前的参数校验
ValidationCheckUtils.check(dto);
好了上面所有的核心代码快已经贴出来了,希望能够帮助到小伙伴们。 其实看到这里的小伙伴们免不了会说上一句使用Spring的 @Valid和@Validated不好嘛,干嘛要自己造轮子呢,多次一举,其实不是这样的这种想法我在编写代码的时候就想到了。但是在我们的业务场景中多个参数接口使用的参数类是同一个,所以使用Spring的@Valid和@Validated自然是不行了。其实换种想法也不是不可以那就是检验参数的再新建一个类,我觉得完全没必要,因为这样很容易就造成类爆炸。不知你是怎么样呢。欢迎评论。