【问题标题】:Create POJO and populate it from Map<String, String>创建 POJO 并从 Map<String, String> 填充它
【发布时间】:2020-02-03 14:59:04
【问题描述】:

在运行时,我想创建一个具有与地图键相同的属性的 POJO,并使用地图的值填充它。
我不知道地图的内容或 POJO 的样子
示例-

Map<String,String> map = new HashMap<>();
map.add("attr1", obj1);
map.add("attr2", obj2);
...

从这个地图我想创建一个 POJO-

class POJO
{
    String attr1;

    public void setAttr1(String attr1) {
        this.attr1 = attr1;
    }

    public String getAttr1() {
        return attr1;
    }

    String attr2;

    public void setAttr2(String attr2) {
        this.attr2 = attr2;
    }

    public String getAttr2() {
        return attr2;
    }

    .....
}

并填充它。 所有这些都应该在运行时发生。 有点像-

Object object = getPopulatedPOJO(map)

Class type = getPOJOType(map);
Object object = type.newInstance();
object = getPopulatedPOJO(map)

【问题讨论】:

  • 填充 POJO 后您的目标是什么?这可能是XY problem
  • @JacobG。我必须将它发送给第三方,其 API 只接受 java 对象。
  • 为什么这可能不是good idea
  • 您的意思是您想要一个具有可变数量字段的 POJO 对象,并在运行时创建这些字段?
  • 谁在提供这个getPOJOType 方法?谁来决定这个方法将返回哪种类型?

标签: java spring class reflection


【解决方案1】:

这不是您问题的最终答案。但我希望这可以为您提供继续前进的方向。请注意,此方向会在运行时修改字节码指令,并可能导致崩溃和失败。

您可以使用javassist,它是一个字节码操纵器。请查看他们的official site 了解更多信息。

需要注意的两个重要事项

  1. 在 Java 中,多个类加载器可以共存,并且每个类加载器都创建自己的命名空间。不同的类加载器可以加载不同的类名相同的类文件

  2. JVM 不允许动态重新加载类。一旦类加载器加载了一个类,它就不能在运行时重新加载该类的修改版本。因此,您不能在 JVM 加载类之后更改它的定义。但是,JPDA(Java 平台调试器架构)提供的重新加载类的能力有限

所以你可以通过两种不同的方式实现你想要的。

  1. 在运行时创建字节码,编写类,使用自定义类加载器从编写的类创建你的 pojo。 javassist 可以帮你解决这个问题,但这对我来说太复杂了。

  2. 使用javassist 修改现有类并使用反射设置值。

对于选项 2,更简单的一个,您可以通过以下方式实现这一目标。

  1. 在你的类路径中添加javassist。如果您使用的是 maven,请在 pom.xml 中添加以下依赖项。
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.21.0-GA</version>
</dependency>
  1. 创建一个您需要使用的虚拟空 pojo 类。让我们称之为Pojo
package com.test;

public class Pojo {
   //Nothing in the source file.
}
  1. 修改类主体以添加来自HashMap 的字段。这是我如何使用您提供的地图的示例。
        Map<String, String> map = new HashMap<String, String>();
        map.put("firstname", "John");
        map.put("lastname", "Doe");

        ClassPool cp = ClassPool.getDefault();
        
        CtClass cc = cp.get("com.test.Pojo");

        // Used for non-primitive data types. If primitive, use CtClass.<inttype, floattype, etc..>
        CtClass strClass = ClassPool.getDefault().get("java.lang.String");

        //Iterate and add all the fields as per the keys in the map
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            CtField field = new CtField(strClass, key, cc);
            field.setModifiers(Modifier.PUBLIC);
            cc.addField(field);
        }

        // Instantiate from the updated class
        Class<Pojo> clazz = cc.toClass();
        Pojo newInstance = clazz.newInstance();

        //Use the map again to set the values using reflection.
        iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            newInstance.getClass().getField(key).set(newInstance, map.get(key));
        }
  1. newInstancePojo 的实例,但是根据映射的键添加字段并根据映射中的值进行设置。使用 jackson ObjectMapper 打印 newInstance 的简单测试会产生此结果。
ObjectMapper objMapper = new ObjectMapper();
String writeValueAsString = objMapper.writeValueAsString(newInstance);
System.out.println(writeValueAsString);

{"firstname":"John","lastname":"Doe"}

希望这会有所帮助。

编辑

如果你想添加get/set方法,你可以在javassist中使用CtMethod创建方法。但是,您只能使用反射来访问它们,因为这些方法是在运行时添加的。

【讨论】:

    【解决方案2】:

    参见answer in a similar question,使用 Jackson objectMapper.convertvalue 方法似乎最合理。

    【讨论】:

    • 这没有回答我的问题,因为我不知道我的 POJO 会是什么!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 1970-01-01
    • 2018-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多