【问题标题】:Jackson deserialisation/TypeReference for dynamically loaded pojo classJackson 反序列化/TypeReference 用于动态加载的 pojo 类
【发布时间】:2018-01-13 02:03:28
【问题描述】:

我需要获取 JSON 输入 Pojo 实例,并且我正在使用 Jackson 2 库及以下 readValue 方法可以使用 typeReference 反序列化:

POJO_ClassName p = mapper.readValue(new TypeReference< POJO_ClassName >() {});

但问题是POJO 是在运行时动态创建和加载的,我如何将JSON 转换为POJO 实例/对象,因为我没有上述语句的完全限定类(POJO_ClassName)名称?

注意:我使用jsonSchema2pojo 库在运行时生成POJO 类。

这是代码 sn-p,我用于在运行时为 JSON 生成 POJO 并尝试

  String classPath="com.EnrichmentService.Thread72"; 
     String classLocation = System.getProperty("user.dir")
                         + "/src/main/java"; JCodeModel codeModel = new JCodeModel();

     final RuleFactory ruleFactory = new RuleFactory(config,
                         new Jackson2Annotator(config), new SchemaStore());

     final SchemaMapper mapperSchema = new SchemaMapper(ruleFactory,
                         new SchemaGenerator());

     mapperSchema.generate(codeModel, "EsRootDoc",classPath, json);

     codeModel.build(new File(classLocation));  // generates pojo classes

     // Till above jsonSchema2Pojo pojo generation all Good !!
      // EsRootDoc instance is needed for further drools drl validations.

     com.EnrichmentService.Thread72.EsRootDoc p = mapper.readValue(new TypeReference<com.EnrichmentService.Thread72.EsRootDoc>() {}); 
// see alternative way as well in my 24Aug17 edit at the end of this question

但由于com.EnrichmentService.Thread72.EsRootDoc 尚未生成,编译器会出错到找不到类。

要点:

1) 在运行时迭代生成相同的 Pojo 类,但随着 JSON 输入的每次变化具有不同的属性。

2) 甚至尝试过 对象 pojo =mapper.readValue(json,Class.forName("com.EnrichmentService.Thread72.EsRootDoc"));因为 class.forName 不会替换现有的类!

Edit 24 Aug17 - 这是我的自定义类加载器:

注意:Indexer 是在运行时加载动态 EsRootDoc/POJO 类的类。

 static class TestClassLoader extends ClassLoader {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if (name.equals("com.EnrichmentService.Thread72.EsRootDoc")) {
                    try {
                        InputStream is = Indexer.class.getClassLoader().getResourceAsStream("com/EnrichmentService/Thread72/EsRootDoc.class");
                        byte[] buf = new byte[is.available()];
                        int len = is.read(buf);

                        Class<?> c=defineClass(name, buf, 0, len);
                        resolveClass(c);
                        return c;


                    } catch (IOException e) {
                        throw new ClassNotFoundException("", e);
                    }
                }
                return getParent().loadClass(name);
            }
        }

我尝试过使用上面的 TestClassLoader 自定义类加载器作为替代方式是这样的:

Class cls = new      TestClassLoader().loadClass("com.EnrichmentService.Thread72.EsRootDoc");
    Object obj = cls.newInstance();
    cls.getMethod("getCrawlerSource").invoke(obj);
    p=mapper.readValue(json, cls);  // but here i am getting the same deserialization exception as earlier.

引用了一个旧答案@How to replace classes in a running application in java ?

Edit2:2017 年 8 月 24 日 stackTrace 面临的异常在这里:https://pastebin.com/ckCu2uWx

【问题讨论】:

  • 我觉得自定义类加载器可以解决这个问题,但问题是大多数类加载器示例我只发现使用自定义类加载器和使用反射调用方法的第一个加载类,但在我的情况下我没有想要调用我的反射方法,但想要 pojo 类的 pojo 实例而不是流口水规则引擎会话。例如:concretepage.com/java/custom-classloader-java-example
  • 我对 jsonSchema2pojo 的理解是它生成(通过 JCodeModel)java 源代码,而不是字节码(我弄错了吗?)。您在哪里/何时编译该源代码? (或者这是问题的一部分?)
  • 好的,谢谢,我想我明白了,您已经负责编译这些生成的源代码并加载类,您只需要帮助了解如何将 Jackson 与动态加载的类一起使用(如标题中所说的 cleary )。
  • 你试过类似Object pojo = new ObjectMapper().readValue(json, Class.forName("com.acme.Pojo"));的东西吗?
  • 嗨 Hugues 。如果是 Object pojo = new ObjectMapper().readValue(json, Class.forName("com.acme.Pojo"));同样,我仍然遇到“com.fasterxml.jackson.databind.JsonMappingException:无法反序列化..”问题。作为现在的更改,我尝试使用您建议的工作流程,并且我没有创建默认的空白 pojo 类,也没有在我的代码中的任何地方使用 pojo 类导入语句或完全限定的类名。但是对于上面的语句得到异常。

标签: java json jackson jsonschema2pojo


【解决方案1】:

Imo 有两种方法可以解决这个问题:

  1. 在编译时创建和编译类(例如,使用 maven 和 jaxb)

  1. 你会这样做:

    String className = "com.EnrichmentService.Thread72.EsRootDoc";
    Class<?> clazz = Class.forName(className);
    Object object = clazz.getConstructor().newInstance();
    Object p = mapper.readValue(json, object.getClass());
    

如果该代码在 mapper.readValue() 之前失败,您还有另一个问题(我的猜测是类加载)。

使用泛型会更好:

    String className = "com.EnrichmentService.Thread72.EsRootDoc";
    Class<?> clazz = Class.forName(className);
    // cannot use dynamically created classes in a static way, just to 
    // show the point
    // com.EnrichmentService.Thread72.EsRootDoc p = 
    //     getObjectFromMessageString(json, clazz);
    Object p = getObjectFromString(json, clazz);

    public static <T> T getObjectFromString(String json, Class<T> clazz) {
        return mapper.readValue(json, clazz);
    }

编辑:

我编写了一些示例代码,它在运行时编译一个类,然后尝试转换为所述编译类的对象。输出如我所料:

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JackonCustomClassTest {
    public static String CLASS_NAME = "EsRootDoc";
    public static String PACKAGE_NAME = "com.EnrichmentService.Thread72";
    public static String CANONICAL_NAME = PACKAGE_NAME + "." + CLASS_NAME;

    public static void main(String args[]) throws Exception {
        JackonCustomClassTest mtc = new JackonCustomClassTest();
        Class<?> c = null;
        String source = null;
        // compile class for the first time
        source = "package "+PACKAGE_NAME+"; public class "+CLASS_NAME+" { public "+CLASS_NAME+"() { }; public String toString() { return \"Name: not existing\" + \" - className: \" + getClass().getCanonicalName(); }; }";
        c = mtc.compileClass(CANONICAL_NAME, source);

        System.out.println("class test: " + c.newInstance().toString());

        // compile class for the second time
        source = "package "+PACKAGE_NAME+"; public class "+CLASS_NAME+" { private String name; public "+CLASS_NAME+"() { }; public String getName() { return name; }; public void setName(String name) { this.name = name; }; public String toString() { return \"Name: \" + name + \" - className: \" + getClass().getCanonicalName(); }; }";
        c = mtc.compileClass(CANONICAL_NAME, source);

        System.out.println("class test: " + c.newInstance().toString());

        mtc.runJackson(c);
    }

    private void runJackson(Class<?> clazz) throws JsonParseException, JsonMappingException, IOException {
        ObjectMapper m = new ObjectMapper();
        String string = "{ \"name\": \"asdf\" }";
        Object o = m.readValue(string, clazz);
        System.out.println("result of conversion: " + o); // Should print "Name: asdf"
    }

    public Class<?> compileClass(String fullyQualifiedClassName, String source) throws Exception {
        // Save source in .java file.
        File root = new java.io.File( "./target/test-classes/" );
        File sourceFile = new File(root, fullyQualifiedClassName.replace(".", "/") + ".java");
        sourceFile.getParentFile().mkdirs();
        Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));

        // Compile source file.
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null, null, null, sourceFile.getPath());

        // Load and instantiate compiled class.
        //          URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
        //          Class<?> cls = Class.forName(fullyQualifiedClassName, true, classLoader);
        Class<?> cls = new TestClassLoader().loadClass(fullyQualifiedClassName);
        return cls;
    }

    static class TestClassLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.startsWith(PACKAGE_NAME)) {
                try {
                    InputStream is = this.getClass().getClassLoader()
                            .getResourceAsStream(name.replace(".",  "/") + ".class");
                    byte[] buf = new byte[is.available()];
                    int len = is.read(buf);

                    Class<?> c = defineClass(name, buf, 0, len);
                    resolveClass(c);
                    return c;

                } catch (IOException e) {
                    throw new ClassNotFoundException("", e);
                }
            }
            return getParent().loadClass(name);
        }
    }
}

编辑 2:

更新了代码以尝试您的 TestClassLoader 类 - 仍然获得该类的正确(更新)版本。

【讨论】:

  • 嗨曼努埃尔感谢您的回复。我仍然收到相同的错误“com.fasterxml.jackson.databind.JsonMappingException:无法反序列化实例”。可能是由于无法通过调用它的类加载器替换已加载的类。请查看以下 class.forName 的答案:stackoverflow.com/questions/26291254/…
  • 好的。但是您是否多次加载该类?还是班级变了?因为如果不是,我不确定为什么会发生此错误。事实:该类是可用的,否则在调用构造函数时您会得到 ClassNotFoundException 或 Exception。这让我再次提出我的两个问题,开始此评论。
  • 是的,EsRootDoc 类在运行时迭代生成,类更改以及每次迭代中的每个输入 json 更改。我确实确保删除所有 .java 文件并重新编译它们,并将目标作为目标文件夹,其中 .class 保存在我的项目中。因此,每次迭代都会替换所有 .java 类和 .class。
  • 嗯,您是否考虑过使用 Rhino 或 Nashorn(Java 的 JS 引擎)来处理您的数据?在那里你会有动态类型,不需要动态类。
  • 也许您的 codeModel 可以返回 Class?然后你可以把它放在 ObjectMapper 中,它应该是最新的(很像我上一个示例代码)。我会再考虑一下这个问题。
【解决方案2】:

您已经发现,您只能将TypeReference 用于编译时已知的类型(没有一些非常棘手的元编程)。

但是,readValue 有许多替代重载,它们不需要 TypeReference,以允许像您这样 TypeReference 不切实际的情况。

我觉得你可以用readValue(... , Class&lt;T&gt; valueType)

如果你对这些后期编译的类有一个特殊的类加载器,那么你可以从中获取一个Class 实例并将其传入,例如:

ClassLoader dynamicallyCompiledPojoLoader = ...;
Class<?> pojoClass = dynamicallyCompiledPojoLoader.loadClass("...");

return mapper.readValue(..., pojoClass);

另请参阅 com.fasterxml.jackson.databind.type.TypeFactory 以了解不使用 TypeReference 指定参数化泛型类型

在“Edit2: 24Aug17 Exception is faces stackTrace is here”之后更新

您当前的异常 (https://pastebin.com/ckCu2uWx) 不是类加载器问题,而是 JSON 架构不匹配问题。

异常信息的相关部分是:

Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
...
through reference chain: com.EnrichmentService.Thread72.EsRootDoc["category"]->java.util.ArrayList[0]->com.EnrichmentService.Thread72.Category["crawler"])

所以Jackson很不高兴JSON中的“crawler”字段是一个对象,即以“{”开头,但是Java属性“crawler”是一个ArrayList,即应该以“[”开头

不知道为什么这里Thread72.Category的POJO结构错了,但是看起来不像是类加载器的问题。

在评论 POJO 类随每个请求更改后更新

您说过 1) POJO 类结构随每个请求而变化,并且 2) 您希望每次都为 POJO 使用相同的类名。

Java - how to load different versions of the same class?

您需要为每个请求使用一个新的类加载器,因为类会被类加载器缓存。到目前为止,您发布的代码表明您正在使用单个类加载器,并希望在每个请求上重新加载“Category”类,这是行不通的。

你说过:

我每次都需要为基于 drools 的 elasticsearch 文档重新索引工作生成新类,而这个 drools 设置需要 pojo/Object 类型的实例。谢谢

...但我认为您应该考虑使用 Map 或类似的 Drools 输入,而不是基于反射的 POJO,因为您事先不知道结构。正如您在这里发现的那样,这不适合类/类加载器抽象。

参见例如

【讨论】:

  • 非常感谢,但在我的情况下,我在代码执行之前也没有具体的 pojo 类。所以我相信还需要一个完全限定的类名说 mapper.readValue(json,EsRootDoc.class);你是这个意思吗?
  • 是的,类似的,或者其他获取 Class 实例的方式。你没有在你的问题中提供足够的细节让我更具体。我已经更新了答案。
  • 嗨,Rich,我希望您可以查看上面的评论线程来提问,这是否会给您带来更好的画面。如果不是,我会更新我的问题。只是不想延长更多的问题,但如果不够清楚,我会的。谢谢
  • 嗨 Rich,我再次编辑了我的答案,请现在检查自定义类加载器信息。
  • 自从您发布堆栈跟踪后,我已经更新了我的答案
【解决方案3】:

但是由于 com.EnrichmentService.Thread72.EsRootDoc 还没有生成,编译器会报错到类 not Found。

是的,当然。如果您的 JVM 中没有类,则无法加载它们。

com.fasterxml.jackson.databind.JsonMappingException: 无法反序列化

请提供所有堆栈跟踪。

1) 在运行时迭代生成相同的 Pojo 类,但随着 JSON 输入的每次变化具有不同的属性。

为什么不使用地图?为什么你不对所有字段都使用一个大类(其中一些会是空值)?

是的,EsRootDoc 类在运行时迭代生成,类更改以及每次迭代中的每个输入 json 更改

如果你在多个线程中这样做,只需同步它们,例如:

final String ConcurrentHashMap<String, Class> dynamicClasses = new ConcurrentHashMap();

Object doInThreadOne(String json, String className) {
    return mapper.readObject(json, dynamicClasses.get(className))

void doInAnotherThread(String className) {
    dynamicClasses.put(className, reload(className));
}

如果你需要更强的一致性,那么你可以使用类名同步:

static final String className = "com.EnrichmentService.Thread72.EsRootDoc";

final String Map<String, Class> dynamicClasses = new HashMap();

Object doInThreadOne(String json) {
    synchronized(className) {
        return mapper.readObject(json, dynamicClasses.get(className))
    }

void doInAnotherThread(String className) {
    synchronized(className) {
        dynamicClasses.put(className, reload(className));
    }
}

方法 Class.forName 使用调用者的类加载器。您使用不同的类加载器,这可能是原因。有重载,你可以在其中传递类加载器。

stackrace 和其他信息的更新。

您应该将 @JsonIgnoreProperties(ignoreUnknown = true) 添加到 Crawler,因为它具有字段 subCategory,而 Pojo 中不存在该字段。

【讨论】:

  • 嗨 egorlitvinenko,1) 堆栈跟踪:pastebin.com/ckCu2uWx 2) 对于嵌套的 JSON 输入,jsonSchema2Pojo 库生成多个 POJO 类。我有一个基于流口水 POJO 的工作流程,我需要 POJO。 3)感谢多线程问题的建议,这很快就会有帮助。
  • 你加载了爬虫类吗?
  • 一般来说,您的 POJO 看起来与 JSON 不匹配。能否也添加生成的 POJO?
  • pastebin.com/gP7V7xQp 这些应该是为 JSON pastebin.com/CBJmWVXa 生成的 POJO 类。我的这个评论也意味着这个 JSON 只是变体之一,而 pastebin 代码是为了给人一种嵌套结构输入的感觉。
  • 将@JsonIgnoreProperties(ignoreUnknown = true) 添加到爬虫
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-08
  • 2016-05-03
  • 1970-01-01
相关资源
最近更新 更多