项目工具:基于ReflectASM+注解开发对象转换工具

开发原因

在项目对接数据中,会遇到了对外标准和内部标准对象转换问题,需要将上报的数据对象转换为我们项目中标准数据对象,当两边数据标准一致时,比较常见的方式,就是new一个标准的对象,setget对接数据;或者orika复制对象。第一种方法,就会有长篇幅的setget方法代码出现,代码不够简洁;也容易遗漏字段的赋值,orika代码简洁,一行代码就可以实现对象的转换。而当标准不一致时题,比如在对接文档中,性别字段为 personGender,而在我们标准中字段却为gender,对于这种问题,还是只能回到第一种方法,在代码中setget,除了这两种情况外,还有上报数据类型和我们标准的类型不一致、上报数据格式为字符串,标准的类型是Integer,针对这个问题,采用了一个基于ReflectASM+注解开发了一个代码转换工具,解决对象转换问题,实现注解配置一行代码转换的功能。

ReflectASM 是一个非常小的 Java 类库,通过ASM框架操作字节码生成来提供高性能的反射处理,自动为 get/set 字段提供访问类,访问类使用字节码操作而不是 Java 的反射技术,因此非常快。

这里为什么使用ReflectASM,并不是使用 Java 的反射技术,反射的性能一直是存在诟病,相比于直接调用速度上差了很多。ReflectASM的调用速度在两者时间,比直接调用慢,但是比使用反射快很多,可以参考,ReflectASM官方提供的基于 Java 7u3测试结果

截取图片_20220527115729

这里,我们可以测试一下java的直接赋值、反射和ReflectASM的赋值

    Class<Person> clazz = Person.class;
    Method method = clazz.getMethod("setPersonName", String.class);
    method.setAccessible(true);

    MethodAccess methodAccess = MethodAccess.get(Person.class);

    Person person = new Person();
    //次数
    int times = 1000000;
    // case0: 直接赋值
    long startTime0 = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
        person.setPersonName("qingshui");
    }
    System.out.println("直接赋值 : " + (System.currentTimeMillis() - startTime0));

    // case1:  java反射
    long startTime1 = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
        method.invoke(person, "qingshui");
    }
    System.out.println("java 反射 : " + (System.currentTimeMillis() - startTime1));

    // case2: reflectasm
    long startTime2 = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
        methodAccess.invoke(person, "setPersonName", "qingshui");
    }
    System.out.println("reflectasm 赋值方法 : " + (System.currentTimeMillis() - startTime2));

测试结果

直接赋值 : 16
java 反射 : 24
reflectasm 赋值方法 : 18

可以看到当遍历次数为100w次时,ReflectASM和直接赋值差距并不是很大

流程图

截取图片_20220527142531

实现

同属性名字段的转换

我们使用A代表源对象、B代表目标对象,在ReflectASM中,MethodAccess为类的方法访问对象,可以通过MethodAccess获取类的各类方法的索引(下标),ReflectASM主要使用调用方法和获取索引,再赋值两种方式操作,其中使用索引的速度最快

MethodAccess access = MethodAccess.get(SomeClass.class);
//直接调用目标方法赋值
access.invoke(someObject, "setName", "abc");
//获取下标赋值
 Integer setIndex = = access.getIndex("setName");
 access.invoke(someObject, setIndex, "abc");

测试ReflectASM两种赋值方式

// case2: reflectasm
long startTime2 = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
    methodAccess.invoke(person, "setPersonName", "qingshui");
}
System.out.println("reflectasm 赋值方法 : " + (System.currentTimeMillis() - startTime2));

// case3: reflectasm 索引
int index2 = methodAccess.getIndex("setPersonName");
long startTime3 = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
    methodAccess.invoke(person, index2, "qingshui");
}
System.out.println("reflectasm  索引 : " + (System.currentTimeMillis() - startTime3));

测试结果:

reflectasm 赋值方法 : 24
reflectasm  索引 : 16

回到主题,当AB的字段的属性名、类型都一致时,如何进行复制操作

 private static final String GET_METHOD = "get";

 private static final String SET_METHOD = "set";

//Object A 方法访问对象
MethodAccess sAccess = MethodAccess.get(A.getClass());
//获取到A的全部字段
List<Field> fields = getFields(A);
//Object B 方法访问对象    
MethodAccess tAccess =  MethodAccess.get(B.getClass());
for (Field field : fields) {
    String fieldName=field.getName();
    //并且属性名称为 name 为getName 
    //StringUtils.capitalize 字符首字母大写
    Integer getIndex = sAccess.getIndex(GET_METHOD + StringUtils.capitalize(fieldName));
    //获取当前数据的值
    Object value = sAccess.invoke(A, getIndex);
    //如果当前的值为空,直接跳过
    if (isNullValue(value)) {
        continue;
    }
    Integer setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName));
    tAccess.invoke(orig, setIndex, value);   
}

其中getFields()isNullValue()可以从附件中查找代码块,当两个属性一致时,步骤如下

  1. 获取A的全部字段(包括父类字段)
  2. 遍历A字段,获取Aget方法的索引
  3. 结合步骤2获取到的索引,获取到A对应字段的值,如何值为空,就没必要进行赋值操作
  4. 获取Bset方法的索引
  5. 集合步骤4的索引,赋值B对象对应字段的值

不同属性名的字段转换

A源对象和B目标对象的字段不一样时,比如想把ApersonName字段的值复制到Bname字段,这里便需要使用之前提到的注解辅助帮忙

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Trans {
    /**
     * 转换字段
     */
    String value();
}

在需要转换的字段上,添加注解

@Trans(value = "name")
private String personName;

代码实现

 private static final String GET_METHOD = "get";

 private static final String SET_METHOD = "set";

//Object A 方法访问对象
MethodAccess sAccess = MethodAccess.get(A.getClass());
//获取到A的全部字段
List<Field> fields = getFields(A);
//Object B 方法访问对象    
MethodAccess tAccess =  MethodAccess.get(B.getClass());
for (Field field : fields) {
    String fieldName=field.getName();
    //并且属性名称为 name 为getName 
    //StringUtils.capitalize 字符首字母大写
    Integer getIndex = sAccess.getIndex(GET_METHOD + StringUtils.capitalize(fieldName));
    //获取当前数据的值
    Object value = sAccess.invoke(A, getIndex);
    //如果当前的值为空,直接跳过
    if (isNullValue(value)) {
        continue;
    }
    String transFieldName;
    //如果存在自定义的注解
    Trans transValue;
    if ((transValue = field.getAnnotation(Trans.class)) != null) {
       transFieldName = transValue.value();
    }else{
        transFieldName=fieldName;
    }
    Integer setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName));
    tAccess.invoke(B, setIndex, value);   
}

在同属性名字段的基础上,添加了一个判断流程

  1. 字段是否存在Trans注解,如果存在注解,则将获取注解配置的value属性值

不同类型的字段

在上个方法中,通过注解的方式解决了字段的类型一致,仅方法属性名不一致的复制,如果当A源对象和B目标对象的字段类型也不一样,该如何处理?这里便需要扩展注解的内容,增加一个目标类型的配置

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Trans {
    /**
     * 转换字段
     */
    String value();

    /**
     * 转换目标的字段类型
     */
    TransType type() default TransType.STRING;

}

public enum TransType {
    /**
     * 字符格式
     */
    STRING,
    /**
     * 整数
     */
    INTEGER

}

这里以Stirng字符格式的值复制给Integer的字段为例子

在需要转换的字段上,添加注解

@Trans(value = "gender",type = TransType.INTEGER)
private String personGender;

代码实现:

 private static final String GET_METHOD = "get";

 private static final String SET_METHOD = "set";

//Object A 方法访问对象
MethodAccess sAccess = MethodAccess.get(A.getClass());
//获取到A的全部字段
List<Field> fields = getFields(A);
//Object B 方法访问对象    
MethodAccess tAccess =  MethodAccess.get(B.getClass());
for (Field field : fields) {
    String fieldName=field.getName();
    //并且属性名称为 name 为getName 
    //StringUtils.capitalize 字符首字母大写
    Integer getIndex = sAccess.getIndex(GET_METHOD + StringUtils.capitalize(fieldName));
    //获取当前数据的值
    Object value = sAccess.invoke(A, getIndex);
    //如果当前的值为空,直接跳过
    if (isNullValue(value)) {
        continue;
    }
    String transFieldName;
    //如果存在自定义的注解
    Trans transValue;
    TransType type;
    if ((transValue = field.getAnnotation(Trans.class)) != null) {
       transFieldName = transValue.value();
       type = transValue.type();
    }else{
        transFieldName=fieldName;
    }
    Object sourceValue;
    //获取到set方法的下标
    Integer setIndex;
    if(type!=null){
         switch (type) {
           case STRING:
                  sourceValue = String.valueOf(value);
                  setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName), String.class);
                  break;
           case INTEGER:
                  sourceValue = Integer.parseInt(String.valueOf(value));
                  setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName), Integer.class);
                  break;  
    }else{
          setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName));     
    }
    tAccess.invoke(B, setIndex, sourceValue);   
}

在上个方法的基础上,增加一个TransType类型的处理,根据TransType获取到目标对象对应的字段、类型的索引,然后再赋值给该字段值,由于TransType的默认值为TransType.STRING;,如果需要将源对象的Integer复制到目标对象Integer时,使用到注解时,需要这样配置

@Trans(value = "gender",type = TransType.INTEGER)
private Integer personGender;

扩展

以上,只是一个简单的转换对象工具的实现,在实际项目中,还需要对这个工具类进行扩展

时间格式处理

这个扩展主要是为了支持Stirng字符格式的值。转换为Date格式,这里涉及到了时间字符的格式化处理,还是要扩展注解的内容

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Trans {
    /**
     * 转换字段
     */
    String value();

    /**
     * 转换目标的字段类型
     */
    TransType type() default TransType.STRING;
	/**
     * 时间格式
     */
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";
}

public enum TransType {
    /**
     * 字符格式
     */
    STRING,
    /**
     * 整数
     */
    INTEGER,
     /**
     * 时间格式
     */
    DATE
}

字段的注解配置

@Trans(value = "startTime", type = TransType.DATE, dateFormat = "yyyy-MM-dd")
private String startTime;

代码实现

 private static final String GET_METHOD = "get";

 private static final String SET_METHOD = "set";

//Object A 方法访问对象
MethodAccess sAccess = MethodAccess.get(A.getClass());
//获取到A的全部字段
List<Field> fields = getFields(A);
//Object B 方法访问对象    
MethodAccess tAccess =  MethodAccess.get(B.getClass());
for (Field field : fields) {
    String fieldName=field.getName();
    //并且属性名称为 name 为getName 
    //StringUtils.capitalize 字符首字母大写
    Integer getIndex = sAccess.getIndex(GET_METHOD + StringUtils.capitalize(fieldName));
    //获取当前数据的值
    Object value = sAccess.invoke(A, getIndex);
    //如果当前的值为空,直接跳过
    if (isNullValue(value)) {
        continue;
    }
    String transFieldName;
    //如果存在自定义的注解
    Trans transValue;
    TransType type;
    if ((transValue = field.getAnnotation(Trans.class)) != null) {
       transFieldName = transValue.value();
       type = transValue.type();
    }else{
        transFieldName=fieldName;
    }
    //需要复制的值
    Object sourceValue;
    //获取到set方法的下标
    Integer setIndex;
    if(type!=null){
         switch (type) {
           case DATE:
                  String formats = transValue.dateFormat();
                 //转换时间格式
                  sourceValue = DateUtil.formatStrToDate(String.valueOf(value), format);
                  setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName), Date.class);
                  break;
           case STRING:
                  sourceValue = String.valueOf(value);
                  setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName), String.class);
                  break;
           case INTEGER:
                  sourceValue = Integer.parseInt(String.valueOf(value));
                  setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName), Integer.class);
                  break;  
    }else{
          setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(transFieldName));     
    }
    tAccess.invoke(B, setIndex, sourceValue);   
}

通过注解的dateFormat属性,扩展工具对字符格式化为时间的处理

处理非基类的List转换List

在实际中,我们会遇到一个List<C.class>转换为另一个List<D.class>的情况,这里还是要继续扩展注解,比如,需要把List<Person> persons转换 List<User> users

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Trans {
    /**
     * 转换字段
     */
    String value();

    /**
     * 转换目标的字段类型
     */
    TransType type() default TransType.STRING;
	/**
     * 时间格式
     */
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";
    /**
     * 转换字段格式
     */
    Class<?> toFieldClass() default String.class;
}

public enum TransType {
    /**
     * 字符格式
     */
    STRING,
    /**
     * 整数
     */
    INTEGER,
     /**
     * 时间格式
     */
    DATE,
     /**
     * 集合
     */
    LIST
}

字段的注解配置

@Trans(value = "users", toFieldClass = User.class)
private List<Person> persons;

@Data
public class Person {
    @Trans(value = "name")
    private String personName;
    
    @Trans(value = "gender",type = TransType.INTEGER)
    private String personGender;
    
    @Trans(value = "startTime", type = TransType.DATE, dateFormat = "yyyy-MM-dd")
    private String startTime;
}

在代码实现中,需要增加如下代码处理

/**
     * TransToExpress::TransToExpress
     * <p>转换数据内容到目标数据
     * <p>HISTORY: 2021/9/13 qingshui : Created.
     *
     * @param dest 源数据
     * @param orig 目标数据
     * @return Object 目标对象
 */
public static Object transToExpress(Object dest, Object orig) {
````取值、判断空值

    Class<?> toFieldClass = transValue.toFieldClass();
    //集合并且非基类转换
    if (value instanceof List && !baseClass(toFieldClass)) {
       listNotBaseClassTypeToList(orig, (List<?>) value, transValue);
       return;
    }
````` 处理非集合的代码
}
  

/*==============  private method =============*/


/**
     * TransToExpress::listNotBaseClassTypeToList
     * <p>TO:非基础类型的集合转换成目标的集合
     * <p>HISTORY: 2021/11/12 qingshui : Created.
     *
     * @param orig       目标值
     * @param value      转换的集合
     * @param transValue 注解
*/
private static void listNotBaseClassTypeToList(Object orig, List<?> value, Trans transValue) {
    String fieldName = transValue.value();
    Class<?> toFieldClass = transValue.toFieldClass();
    List<?> values = transListToListExpress(toFieldClass, value);
    //处理赋值 
    invokeTargetValue(orig, fieldName, values, TransType.LIST);
}

/**
     * TransToExpress:: transListToListExpress
     * <p>list集合转list
     * <p>通过ConstructorAccess构造函数,构造对象,便利调用transToExpress方法
     * <p>HISTORY: 2021/9/16 qingshui : Created.
     *
     * @param orig  目标的对象的class对象
     * @param value 转换之前的值
     * @return List<Object>  返回转换后的对象集合
*/
private static List<Object> transListToListExpress(Class<?> orig, List<?> value) {
    List<Object> values = new ArrayList<>();
    //从缓存获取到构造对象
    ConstructorAccess<?> access = ConstructorAccess.get(orig);
    for (Object dest : value) {
        Object newOrig = access.newInstance();
        values.add(transToExpress(dest, newOrig));
    }
    return values;
}

/**
     * TransToExpress:: baseClass
     * <p>判断传入的是不是基础类型和系统类
     * <p>HISTORY: 2021/9/17 qingshui : Created.
     *
     * @param className 类名
     * @return boolean  true 是的 false 不是
*/
private static boolean baseClass(Class<?> className) {
    // org.springframework.util.ClassUtils 方法
    return className.equals(String.class) || ClassUtils.isPrimitiveOrWrapper(className);
}


/**
     * TransToExpress:: invokeTargetValue
     * <p>使用asm对目标的对象进行赋值操作、
     * <p>HISTORY: 2021/9/15 qingshui : Created.
     *
     * @param orig      目标的对象值
     * @param fieldName 赋值的字段名称
     * @param value     目标值
     * @param type      转换的类型,这里其实可以不传入该值进行推断,也能获取到,考虑传入类型,可以准确定位到方法的下标
 */
private static void invokeTargetValue(Object orig, String fieldName, Object value, TransType type) {
    try {
        MethodAccess tAccess = getMethodAccess(orig);
        //获取到set方法的下标
        Object sourceValue;

        Integer setIndex;
        switch (type) {
            case DATE:
                sourceValue = value;
                setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), Date.class);
                break;
            case STRING:
                sourceValue = String.valueOf(value);
                setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), String.class);
                break;
            case INTEGER:
                sourceValue = Integer.parseInt(String.valueOf(value));
                setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), Integer.class);
                break;
            case LIST:
 				//增加一个list对象的赋值
                sourceValue = value;
                setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), List.class);
                break;
            default:
                setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName));
                sourceValue = value;
        }
        //赋值
        tAccess.invoke(orig, setIndex, sourceValue);
    } catch (IllegalArgumentException e) {
        //打印日志
        log.error("TransToExpress.invokeTargetValue fieldName: {} \n Exception :{}", fieldName, e.getMessage());
    }
}

处理非基类的集合类型转换,步骤如下

  1. 判断当前的需要转换的值是否为List,并且转换的目标类toFieldClass是否为基类,如何是基类的转换,直接调用invokeTargetValue 进行转换,不需要特殊的处理
  2. 使用ConstructorAccess构建目标类对象
  3. 遍历value,调用transToExpress方法转换源对象A转换为目标对象B,递归调用,transToExpress方法(工具类的入口)

优化

使用缓存

由于在代码中会频繁使用MethodAccessFieldConstructorAccess等对象,所以在实际项目中,会使用map缓存,增加复用,提高效率

    /**
     * 方法的的缓存
     */
    private static final Map<Class<?>, MethodAccess> ACCESS_MAP = new HashMap<>(64);

    /**
     * 字段的缓存
     */
    private static final Map<Class<?>, List<Field>> FIELDS_MAP = new HashMap<>(64);

    /**
     * 下标的缓存
     */
    private static final Map<String, Integer> INDEX_MAP = new HashMap<>(64);

    /**
     * 构建对象方法的的缓存
     */
    private static final Map<Class<?>, ConstructorAccess<?>> CONSTRUCTOR_ACCESS_MAP = new HashMap<>(64);

//使用缓存的例子 case1
public static Object transToExpress(Object dest, Object orig) {
    //缓存
    MethodAccess sAccess = getMethodAccess(dest);
    //拿到类的源对象的全部属性
    List<Field> fields = getFields(dest);
    for (Field field : fields) {
        //获取字段值的所在的下标
        String getKey = dest.getClass().getName() + GET_METHOD + field.getName();
        Integer getIndex = INDEX_MAP.get(getKey);
        if (getIndex == null) {
            getIndex = sAccess.getIndex(GET_METHOD + StringUtils.capitalize(field.getName()));
            INDEX_MAP.put(getKey, getIndex);
        }
    }     
}

//使用缓存的例子 case2
 private static void invokeTargetValue(Object orig, String fieldName, Object value, TransType type) {
        try {
            MethodAccess tAccess = getMethodAccess(orig);
            //获取到set方法的下标
            Object sourceValue;
            String setKey = orig.getClass().getName() + SET_METHOD + fieldName;
            Integer setIndex = INDEX_MAP.get(setKey);
            switch (type) {
                case DATE:
                    sourceValue = value;
                    if (setIndex == null) {
                        //获取到赋值方法的下标
                        setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), Date.class);
                        INDEX_MAP.put(setKey, setIndex);
                    }
                    break;
                case STRING:
                    sourceValue = String.valueOf(value);
                    if (setIndex == null) {
                        setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), String.class);
                        INDEX_MAP.put(setKey, setIndex);
                    }
                    break;
                case INTEGER:
                    sourceValue = Integer.parseInt(String.valueOf(value));
                    if (setIndex == null) {
                        setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), Integer.class);
                        INDEX_MAP.put(setKey, setIndex);
                    }
                    break;
                case LIST:
                    sourceValue = value;
                    if (setIndex == null) {
                        setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName), List.class);
                        INDEX_MAP.put(setKey, setIndex);
                    }
                    break;
                default:
                    if (setIndex == null) {
                        setIndex = tAccess.getIndex(SET_METHOD + StringUtils.capitalize(fieldName));
                        INDEX_MAP.put(setKey, setIndex);
                    }
                    sourceValue = value;
            }
            //赋值
            tAccess.invoke(orig, setIndex, sourceValue);
        } catch (IllegalArgumentException e) {
            //打印日志
 			log.error("TransToExpress.invokeTargetValue fieldName: {} \n Exception :{}", fieldName, e.getMessage());
        }
    }


/*==============  private method =============*/
  

/**
     * TransToExpress:: getMethodAccess
     * <p>TO:获取到对象的方法的句柄
     * <p>HISTORY: 2021/11/12 qingshui : Created.
     *
     * @param object 对象
     * @return MethodAccess  返回方法的句柄
*/
private static MethodAccess getMethodAccess(Object object) {
    MethodAccess sAccess = ACCESS_MAP.get(object.getClass());
    if (sAccess == null) {
        sAccess = MethodAccess.get(object.getClass());
        ACCESS_MAP.put(object.getClass(), sAccess);
    }
    return sAccess;
}

  /**
     * TransToExpress:: getDestFields
     * <p>TO:获取到源对象的字段值
     * <p>HISTORY: 2021/11/12 qingshui : Created.
     *
     * @param dest 源对象
     * @return List<Field>   字段属性.
     */
private static List<Field> getFields(Object dest) {
    List<Field> fields = FIELDS_MAP.get(dest.getClass());
    if (fields == null) {
        fields = TransUtil.getFields(dest.getClass());
        FIELDS_MAP.put(dest.getClass(), fields);
    }
    return fields;
}

总结

文章大部分代码以参考为主,在实际项目,我们扩展这个工具类其他功能,比如增加主键自定义格式化、图片上传的自定义处理、赋值额外字段等功能。在附件中,提供了一个最基础转换工具类,在开发这个工具类的过程中,逐步完善工具,丰富功能,体验到了开发轮子魅力所在,不过,最大成就感还是来自这行代码

//一行代码转换对象
TransToExpress.transToExpress(person, user)

附件

    /*==============  private method =============*/

    /**
     *  <p>TO:获取一个类的全部字段信息
     *  <p>HISTORY: 2022/2/28 qingshui : Created.
     *  @param    objClass 目标类
     *  @return    List<Field> 字段的信息
     */
    private static List<Field> getFields(Class<?> objClass) {
        List<Field> fieldList = new ArrayList<>();
        while (null != objClass) {
            fieldList.addAll(Arrays.asList(objClass.getDeclaredFields()));
            objClass = objClass.getSuperclass();
        }
        return fieldList;
    }
    
    /**
     * <p>TO:判断当前字符是否是空值,这里直接转拆箱转换为String,或者判断其他对象是不是null值
     * <p>HISTORY: 2021/11/12 qingshui : Created.
     *
     * @param value 需要判断的值
     * @return Boolean true
     */
    private static boolean isNullValue(Object value) {
        if (value instanceof String) {
            return StringUtils.isBlank((String) value);
        }
        return null == value;
    }