如何使用反射进行参数效验 | Java Debug 笔记

2021-05-15 10:15:57 Author: juejin.cn
觉得文章还不错?,点我收藏



本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接

前言

为什么会有这种功能出现呢!

各位可能会有疑问,为什么不使用 @Valid 注解呢!各位兄弟我也想用啊!但是没办法啊!项目性质导致的。项目会对接各种渠道方,但是所有渠道方都是用同一个实体进行传递的(通用性),但是呢,每个渠道方对字段必传的效验又不一样(用户是上帝)。比如公用实体里有 a、b、c 属性。AA 渠道方只传递 a、b 属性,c 属性没办法给,那我们对 AA 渠道只能给 a、b 属性做必传效验。然后 BB 渠道方给 b、c 属性,那我们对 BB 渠道只能给 b、c 属性做必传效验。也不可能用 if/else一个一个去判断的,字段属性一多,会凉的。基于这种场景就出现了使用 反射 做效验。

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

@Slf4j
public class CheckUtil {

    /**
     * 效验属性类型为 Object
     * @param sourceObject 源对象
     * @param checkFields 需要效验的字段集合
     * @param sourceObject
     * @param checkFields
     * @return
     */
    public static String checkObjNull(Object sourceObject, List<String> checkFields){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        Class<?> objClass = sourceObject.getClass();
        // 获得本类所有属性对象
        Field[] declaredFields = objClass.getDeclaredFields();
        if(null == declaredFields || declaredFields.length == 0){
            return "源对象中没有属性";
        }

        StringBuilder sb = new StringBuilder();
        for (String checkField : checkFields) {
            for (Field field : declaredFields) {
                //关闭程序的安全检测
                field.setAccessible(true);
                // 效验字段与 属性名称相等 则进行效验
                String fieldName = field.getName();
                if(checkField.equals(fieldName)){
                    try {
                        // 获得字段属性值
                        Object fieldValue = field.get(sourceObject);
                        // 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
                        if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
                            sb = sb.append("[").
                                    append(fieldName).
                                    append("]").
                                    append("为必传字段");
                            return sb.toString();
                        }
                    } catch (IllegalAccessException e) {
                       log.error("获取属性值异常",e);
                       return "请检查传递的源对象";
                    }
                }
            }
        }
        // 通过效验
        return null;
    }

    /**
     * 效验属性类型为 List
     * @param sourceObject 源对象
     * @param checkFields 需要效验的字段集合
     * @param listPropertyName 源对象的属性名称
     * @return
     */
    public static String checkArrayNull(Object sourceObject, List<String> checkFields,String listPropertyName){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        if(sourceObject instanceof List){
          List list = (List) sourceObject;
            if(CollectionUtils.isEmpty(list)){
                return listPropertyName + "为必传字段";
            }
            for (Object obj : list) {
                String resultStr = checkObjNull(obj, checkFields);
                return resultStr;
            }
        }
        return null;
    }
}
复制代码

好了,工具类已经编写好了。那怎么进行使用呢!

使用

导入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
复制代码

DTO

创建三个类,分别是 UserInfoDTOBankCardDTOIdCardDTO

  • UserInfoDTO:用户信息
@Data
@ApiModel("用户信息")
public class UserInfoDTO {

    @ApiModelProperty("用户唯一号")
    private Long userId;

    @ApiModelProperty("姓名")
    private String name;

    @ApiModelProperty("银行卡列表信息")
    private List<BankCardDTO> cardDTOList;

    @ApiModelProperty("身份证信息")
    private IdCardDTO idCardDTO;
}
复制代码
  • BankCardDTO:银行卡列表信息
@Data
@ApiModel("银行卡列表")
public class BankCardDTO {

    @ApiModelProperty("卡号")
    private String cardNumber;

    @ApiModelProperty("银行名称")
    private String bankName;

}
复制代码
  • IdCardDTO:身份证信息
@Data
@ApiModel("身份证信息")
public class IdCardDTO {

    @ApiModelProperty("证件号")
    private String idNumber;
    @ApiModelProperty("有效期")
    private String validity;
}
复制代码

Controller

然后再创建一个名为TestController的类,提供一个saveUser方法,方法中调用编写的工具类。

 @PostMapping(value = "/saveUser")
    private String saveUser(@RequestBody UserInfoDTO req){
        //效验 用户信息
        String check = CheckUtil.checkObjNull(req, Arrays.asList("userId", "name", "cardDTOList", "idCardDTO"));
        if(StringUtils.hasLength(check)){
            return check;
        }
        //效验 用户信息内的 银行卡列表
        List<BankCardDTO> cardDTOList = req.getCardDTOList();
        String check2 = CheckUtil.checkArrayNull(cardDTOList, Arrays.asList("cardNumber", "bankName"),"cardDTOList");
        if(StringUtils.hasLength(check2)){
            return check2;
        }
        //效验 用户信息内的 身份证信息
        IdCardDTO idCardDTO = req.getIdCardDTO();
        String check3 = CheckUtil.checkObjNull(idCardDTO, Arrays.asList("idNumber", "validity"));
        if(StringUtils.hasLength(check3)){
            return check3;
        }
        return "成功";
    }
复制代码

调用方法

请求地址:localhost:8080/api/saveUser
{
    "userId": "1",
    "name": "1",
    "cardDTOList": [
        {
            "cardNumber": "5",
            "bankName": ""
        }
    ],
    "idCardDTO": {
        "idNumber": "55",
        "validity": "4"
    }
}
响应:
[bankName]为必传字段
复制代码

在我们项目组这效果以及足够使用了。因为不直接对接前端,我们只需要将响应给对接方就好了。有些小伙伴可能会直接对接前端,将响应信息展示在前端了。那肯定不行,用户怎么知道 [bankName]为必传字段这是个啥!肯定是需要提示中文的,例如:[银行名称]为必传字段

提示信息优化

如果要提示中文呢!也是有办法的。各位小伙伴可能注意到了,我导入了swagger的依赖,其中有个注解为 @ApiModelProperty,这注解写在了每个属性上,为每个属性提供了描述信息,所以我们可以取这个注解的值。

public static String checkObjNull(Object sourceObject, List<String> checkFields){
        if(Objects.isNull(sourceObject)){
            return "请输入正确源对象";
        }
        if(CollectionUtils.isEmpty(checkFields)){
            return "效验字段不能为空";
        }
        Class<?> objClass = sourceObject.getClass();
        // 获得本类所有属性对象
        Field[] declaredFields = objClass.getDeclaredFields();
        if(null == declaredFields || declaredFields.length == 0){
            return "源对象中没有属性";
        }

        StringBuilder sb = new StringBuilder();
        for (String checkField : checkFields) {
            for (Field field : declaredFields) {
                //关闭程序的安全检测
                field.setAccessible(true);
                // 效验字段与 属性名称相等 则进行效验
                String fieldName = field.getName();
                // 获得指定注解
                ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
                // 获得 value属性 的值
                String value = apiModelProperty.value();
                if(checkField.equals(fieldName)){
                    try {
                        // 获得字段属性值
                        Object fieldValue = field.get(sourceObject);
                        // 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
                        if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
                            sb = sb.append("[").
                                    append(value).
                                    append("]").
                                    append("为必传字段");
                            return sb.toString();
                        }
                    } catch (IllegalAccessException e) {
                        log.error("获取属性值异常",e);
                        return "请检查传递的源对象";
                    }
                }
            }
        }
        // 通过效验
        return null;
    }
复制代码

再次调用方法

请求地址:localhost:8080/api/saveUser
{
    "userId": "1",
    "name": "1",
    "cardDTOList": [
        {
            "cardNumber": "5",
            "bankName": ""
        }
    ],
    "idCardDTO": {
        "idNumber": "55",
        "validity": "4"
    }
}
响应:
[银行名称]为必传字段
复制代码
  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。



觉得文章还不错?,点我收藏



如果文章侵犯到您的版权,请联系我:buaq.net[#]pm.me