Java Bean 转 Map 的巨坑,注意了!!!(2)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Java Bean 转 Map 的巨坑,注意了!!!(2)

三、解决办法


3.1 解决方案

解决方案有很多,本文提供一个基于 dubbo的解决方案。


maven 依赖:


<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>3.0.9</version>
</dependency>


示例代码:


import org.apache.dubbo.common.utils.PojoUtils;
import third.fastjson.MockObject;
import java.util.Date;
public class DubboPojoDemo {
    public static void main(String[] args) {
        MockObject mockObject = new MockObject();
        mockObject.setAInteger(1);
        mockObject.setALong(2L);
        mockObject.setADate(new Date());
        mockObject.setADouble(3.4D);
        mockObject.setParent(3L);
        Object generalize = PojoUtils.generalize(mockObject);
        System.out.println(generalize);
    }
}


调试效果:


1.png


Java Visualizer 效果:


2.png


3.2 原理解析

核心代码: org.apache.dubbo.common.utils.PojoUtils#generalize(java.lang.Object)


public static Object generalize(Object pojo) {
     eturn generalize(pojo, new IdentityHashMap());
}


关键代码:


// pojo 待转换的对象
// history 缓存 Map,提高性能
private static Object generalize(Object pojo, Map<Object, Object> history) {
    if (pojo == null) {
        return null;
    }
     // 枚举直接返回枚举名
    if (pojo instanceof Enum<?>) {
        return ((Enum<?>) pojo).name();
    }
    // 枚举数组,返回枚举名数组
    if (pojo.getClass().isArray() && Enum.class.isAssignableFrom(pojo.getClass().getComponentType())) {
        int len = Array.getLength(pojo);
        String[] values = new String[len];
        for (int i = 0; i < len; i++) {
            values[i] = ((Enum<?>) Array.get(pojo, i)).name();
        }
        return values;
    }
    // 基本类型返回 pojo 自身
    if (ReflectUtils.isPrimitives(pojo.getClass())) {
        return pojo;
    }
    // Class 返回 name
    if (pojo instanceof Class) {
        return ((Class) pojo).getName();
    }
    Object o = history.get(pojo);
    if (o != null) {
        return o;
    }
    history.put(pojo, pojo);
// 数组类型,递归
    if (pojo.getClass().isArray()) {
        int len = Array.getLength(pojo);
        Object[] dest = new Object[len];
        history.put(pojo, dest);
        for (int i = 0; i < len; i++) {
            Object obj = Array.get(pojo, i);
            dest[i] = generalize(obj, history);
        }
        return dest;
    }
// 集合类型递归
    if (pojo instanceof Collection<?>) {
        Collection<Object> src = (Collection<Object>) pojo;
        int len = src.size();
        Collection<Object> dest = (pojo instanceof List<?>) ? new ArrayList<Object>(len) : new HashSet<Object>(len);
        history.put(pojo, dest);
        for (Object obj : src) {
            dest.add(generalize(obj, history));
        }
        return dest;
    }
    // Map 类型,直接 对 key 和 value 处理
    if (pojo instanceof Map<?, ?>) {
        Map<Object, Object> src = (Map<Object, Object>) pojo;
        Map<Object, Object> dest = createMap(src);
        history.put(pojo, dest);
        for (Map.Entry<Object, Object> obj : src.entrySet()) {
            dest.put(generalize(obj.getKey(), history), generalize(obj.getValue(), history));
        }
        return dest;
    }
    Map<String, Object> map = new HashMap<String, Object>();
    history.put(pojo, map);
    // 开启生成 class 则写入 pojo 的class
    if (GENERIC_WITH_CLZ) {
        map.put("class", pojo.getClass().getName());
    }
  // 处理 get 方法
    for (Method method : pojo.getClass().getMethods()) {
        if (ReflectUtils.isBeanPropertyReadMethod(method)) {
            ReflectUtils.makeAccessible(method);
            try {
                map.put(ReflectUtils.getPropertyNameFromBeanReadMethod(method), generalize(method.invoke(pojo), history));
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
    // 处理公有属性
    for (Field field : pojo.getClass().getFields()) {
        if (ReflectUtils.isPublicInstanceField(field)) {
            try {
                Object fieldValue = field.get(pojo);
                // 对象已经解析过,直接从缓存里读提高性能
                if (history.containsKey(pojo)) {
                    Object pojoGeneralizedValue = history.get(pojo);
                    // 已经解析过该属性则跳过(如公有属性,且有 get 方法的情况)
                    if (pojoGeneralizedValue instanceof Map
                        && ((Map) pojoGeneralizedValue).containsKey(field.getName())) {
                        continue;
                    }
                }
                if (fieldValue != null) {
                    map.put(field.getName(), generalize(fieldValue, history));
                }
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
    return map;
}


关键截图  


3.png

4.png

org.apache.dubbo.common.utils.ReflectUtils#getPropertyNameFromBeanReadMethod
public static String getPropertyNameFromBeanReadMethod(Method method) {
    if (isBeanPropertyReadMethod(method)) {
        // get 方法,则从 index =3 的字符小写 + 后面的字符串
        if (method.getName().startsWith("get")) {
            return method.getName().substring(3, 4).toLowerCase()
                    + method.getName().substring(4);
        }
        // is 开头方法, index =2 的字符小写 + 后面的字符串
        if (method.getName().startsWith("is")) {
            return method.getName().substring(2, 3).toLowerCase()
                    + method.getName().substring(3);
        }
    }
    return null;
}


因此, getALong 方法对应的属性名被解析为 aLong。


同时,这么处理也会存在问题。如当属性名叫 URL 时,转为 Map 后 key 就会被解析成 uRL。


2.png


从这里看出,当属性名比较特殊时也很容易出问题,但 dubbo 这个工具类更符合我们的预期。 更多细节,大家可以根据 DEMO 自行调试学习。


如果想严格和属性保持一致,可以使用反射获取属性名和属性值,加缓存机制提升解析的效率。


四、总结


Java Bean 转 Map 的坑很多,最常见的就是类型丢失和属性名解析错误的问题。 大家在使用 JSON 框架和 Java Bean 转 Map 的框架时要特别小心。 平时使用某些框架时,多写一些 DEMO 进行验证,多读源码,多调试,少趟坑。


相关文章
|
28天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
40 3
|
28天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
57 2
|
28天前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
81 2
|
18天前
|
存储 Java API
Java交换map的key和value值
通过本文介绍的几种方法,可以在Java中实现Map键值对的交换。每种方法都有其优缺点,具体选择哪种方法应根据实际需求和场景决定。对于简单的键值对交换,可以使用简单遍历法或Java 8的Stream API;对于需要处理值不唯一的情况,可以使用集合存储或Guava的Multimap。希望本文对您理解和实现Java中的Map键值对交换有所帮助。
23 1
|
26天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
44 4
|
26天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
50 3
|
26天前
|
存储 Java API
详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
【10月更文挑战第19天】深入剖析Java Map:不仅是高效存储键值对的数据结构,更是展现设计艺术的典范。本文从基本概念、设计艺术和使用技巧三个方面,详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
43 3
|
26天前
|
存储 缓存 安全
在Java的Map家族中,HashMap和TreeMap各具特色
【10月更文挑战第19天】在Java的Map家族中,HashMap和TreeMap各具特色。HashMap基于哈希表实现,提供O(1)时间复杂度的高效操作,适合性能要求高的场景;TreeMap基于红黑树,提供O(log n)时间复杂度的有序操作,适合需要排序和范围查询的场景。两者在不同需求下各有优势,选择时需根据具体应用场景权衡。
29 2
|
26天前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
24 2
|
26天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1