如何更优雅的判断参数不为空?
自定义一个ValidationUtil参数验证工具类,它通过反射机制实现了对数据传输对象(DTO)中字段的自动验证。可以省去在 service 层中对参数不为 null 的校验
1. 实现原理
ValidationUtil主要通过以下技术实现参数验证:
1.1 核心验证方法 validateParameters
package com.yixueji.utils;
import com.yixueji.exception.ParameterException;
import com.yixueji.constant.MessageConstant;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 参数验证工具类
* 提供通用的参数验证方法,用于检查数据传输对象(DTO)中的字段是否有效
*/
public class ValidationUtil {
/**
* 验证指定字段的参数是否有效
* 该方法通过反射调用dto中的getter方法来检查指定字段的值是否为null或空字符串
* 如果任何一个字段的值为null或空字符串,将抛出ParameterException异常
*
* @param dto 数据传输对象,包含了需要验证的字段
* @param fields 需要验证的字段名数组
* @throws ParameterException 如果任何一个字段的值为null或空字符串
*/
public static void validateParameters(Object dto, String... fields) {
// 遍历需要验证的字段数组
for (String field : fields) {
try {
// 构造getter方法名,JavaBean规范通常使用get作为前缀
String getterMethodName = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
// 使用反射调用getter方法获取字段值
//invoke(dto) 的意思是:
//“在 dto 这个对象上,调用之前通过反射找到的方法”。
//相当于直接调用 dto.方法名(),只不过是通过反射实现的。
Object value = dto.getClass().getMethod(getterMethodName).invoke(dto);
// 检查字段值是否为null或空字符串
if (value == null || (value instanceof String && ((String) value).isEmpty())) {
// 如果值为null或空字符串,抛出ParameterException异常
throw new ParameterException(MessageConstant.PARAMETER_ERROR+":"+field);
}
} catch (NoSuchMethodException e) {
throw new ParameterException("字段 " + field + " 没有对应的 getter 方法");
} catch (IllegalAccessException e) {
throw new ParameterException("无权访问字段 " + field + " 的 getter 方法");
} catch (InvocationTargetException e) {
throw new ParameterException("获取字段 " + field + " 的值时出错: " + e.getCause().getMessage());
}
}
}
/**
* 验证字符串是否为空
*
* @param value 需要验证的字符串
* @return 如果字符串为null或空,返回true;否则返回false
*/
public static boolean isEmpty(String value) {
return value == null || value.trim().isEmpty();
}
/**
* 验证对象是否为空
*
* 此方法通过反射检查对象的所有字段是否为空,用于判断一个对象是否真的“空”
* 它不仅检查对象是否为null,还检查对象的每个字段是否为null或空字符串
*
* @param obj 需要验证的对象
* @return 如果对象为null或所有字段都为空,返回true;否则返回false
*/
public static boolean isObjectEmpty(Object obj) {
if (obj == null) {
return true;
}
// 获取对象的所有字段(包括私有字段)
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
try {
// 设置可访问(避免 private 字段报错)
field.setAccessible(true);
// 获取字段值
Object value = field.get(obj);
// 检查字段是否非空
if (value != null && !value.toString().trim().isEmpty()) {
return false; // 只要有一个字段非空,就不是空对象
}
} catch (IllegalAccessException e) {
throw new RuntimeException("反射访问字段失败", e);
}
}
return true; // 所有字段都为空
}
/**
* 验证对象是否为null
*
* @param obj 需要验证的对象
* @return 如果对象为null,返回true;否则返回false
*/
public static boolean isNull(Object obj) {
return obj == null;
}
/**
* 验证集合或数组是否为空
*
* @param collection 需要验证的集合或数组
* @return 如果集合为null或空,返回true;否则返回false
*/
public static boolean isEmpty(Object[] collection) {
return collection == null || collection.length == 0;
}
}
这个方法使用了Java反射API来动态调用DTO对象的getter方法,具体步骤:
- 接收一个DTO对象和需要验证的字段名数组
- 遍历字段名数组,为每个字段构造标准的JavaBean getter方法名
- 通过反射获取并调用getter方法获取字段值
- 检查字段值是否为null或空字符串
- 如果验证失败,抛出自定义的ParameterException异常
1.2 辅助验证方法
工具类还提供了多个辅助验证方法:
isEmpty(String)
: 验证字符串是否为空isObjectEmpty(Object)
: 通过反射检查对象的所有字段是否为空isNull(Object)
: 验证对象是否为nullisEmpty(Object[])
: 验证集合或数组是否为空
2. 封装成工具类的好处
2.1 代码复用与简化
原始代码:
if (userRegisterDTO.getUserName() == null ||
userRegisterDTO.getUserName().isEmpty() ||
userRegisterDTO.getPassword() == null ||
userRegisterDTO.getPassword().isEmpty() ||
userRegisterDTO.getPhone() == null ||
userRegisterDTO.getPhone().isEmpty()) {
throw new ParameterException(MessageConstant.PARAMETER_ERROR);
}
使用工具类后:
ValidationUtil.validateParameters(userRegisterDTO, "userName", "password", "phone");
显而易见,代码量大幅减少,可读性显著提高。
2.2 统一异常处理
- 所有参数验证失败都抛出统一的
ParameterException
异常 - 异常消息格式统一,包含具体的错误字段信息
- 便于全局异常处理器进行捕获和处理
2.3 灵活性与可扩展性
- 支持任意类型的DTO对象验证
- 可以验证任意数量的字段
- 易于扩展新的验证规则和方法
2.4 提高代码质量
- 减少重复代码,符合DRY原则(Don't Repeat Yourself)
- 提高代码可维护性,验证逻辑集中在一处
- 降低出错几率,统一的验证规则减少人为错误
2.5 增强安全性
- 确保关键参数不会为空,防止空指针异常
- 防止恶意用户提交空参数进行攻击
- 提供详细的错误信息,便于问题定位
2.6 提高开发效率
- 开发者无需编写重复的参数检查代码
- 减少样板代码,专注于业务逻辑实现
- 统一的验证方式,降低学习成本
3. 实际应用场景
3.1 用户登录参数验证
// 验证用户登录参数的有效性
ValidationUtil.validateParameters(userLoginDTO, "userName", "password");
3.2 用户注册参数验证
ValidationUtil.validateParameters(userRegisterDTO, "userName", "password", "phone");
3.3 密码修改参数验证
ValidationUtil.validateParameters(userPasswordDTO, "oldPassword", "newPassword");
4. 技术亮点
4.1 反射机制的灵活应用
通过Java反射API动态调用getter方法,避免了硬编码字段访问,使工具类具有通用性。
4.2 可变参数的使用
使用String... fields
可变参数,允许验证任意数量的字段,提高了API的灵活性。
4.3 异常处理的完善
不仅捕获了反射可能产生的各种异常,还将它们转换为业务异常,提供了更友好的错误信息。
4.4 符合面向对象设计原则
- 单一职责原则:工具类专注于参数验证
- 开闭原则:易于扩展新的验证方法,无需修改现有代码
- 依赖倒置原则:通过接口(Object)而非具体实现进行操作
5. 潜在的改进空间
- 添加更复杂的验证规则,如邮箱格式、手机号格式等
- 支持嵌套对象的验证
- 考虑使用注解驱动的验证方式,如Bean Validation API
- 添加批量验证并收集所有错误的能力,而非首次失败就抛出异常
总结
ValidationUtil工具类通过反射机制实现了对DTO对象参数的自动验证,大大简化了代码,提高了开发效率和代码质量。它将分散在各处的参数验证逻辑集中管理,使代码更加简洁、可维护,并提供了统一的异常处理机制。这种设计不仅符合软件工程的最佳实践,也体现了"高内聚、低耦合"的设计思想。
评论区