GraalVM原生镜像中Jackson无法反序列化JsonNode为泛型ConcurrentHashMap
问题背景
自定义Jackson反序列化器在JVM运行Jar包时完全正常,但通过GraalVM native-image工具转为原生镜像后,在convertValue调用处抛出异常:
java.lang.IllegalArgumentException: Cannot construct instance of `java.util.concurrent.ConcurrentHashMap` (no Creators, like default constructor, exist): no default constructor found at [Source: UNKNOWN; byte offset: #UNKNOWN] at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4449) at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4400) at myclient.sub.Subscriber$Deserializer.deserialize(Subscriber.java:78) at myclient.sub.Subscriber$Deserializer.deserialize(Subscriber.java:46) at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4444) at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4380)
尝试使用MapType替代TypeReference后问题仍存在,环境为Oracle GraalVM 21.0.2+13.1。
解决方案
1. 使用当前反序列化上下文的ObjectMapper
不要在反序列化器中新建ObjectMapper实例,而是复用当前上下文已配置好的ObjectMapper(通过jp.getCodec()获取),它会继承所有针对GraalVM原生镜像的适配配置:
// 替换原代码中(new ObjectMapper()).convertValue(...)的部分 ObjectMapper mapper = (ObjectMapper) jp.getCodec(); accounts = mapper.convertValue(v, new TypeReference<ConcurrentHashMap<Integer, Account>>() {});
2. 显式配置ObjectMapper支持ConcurrentHashMap
如果必须新建ObjectMapper,需要显式注册ConcurrentHashMap的反序列化支持,确保GraalVM能识别其构造逻辑:
ObjectMapper mapper = new ObjectMapper(); // 注册ConcurrentHashMap的反序列化器 mapper.registerModule(new SimpleModule() .addDeserializer(ConcurrentHashMap.class, new MapDeserializer( mapper.getTypeFactory().constructMapType(ConcurrentHashMap.class, Integer.class, Account.class), null, null, null ))); accounts = mapper.convertValue(v, new TypeReference<ConcurrentHashMap<Integer, Account>>() {});
3. 添加GraalVM原生镜像配置文件
创建src/main/resources/META-INF/native-image/com.fasterxml.jackson.core/jackson-databind/reflect-config.json文件,告诉GraalVM保留ConcurrentHashMap的构造器元数据:
[ { "name": "java.util.concurrent.ConcurrentHashMap", "allDeclaredConstructors": true, "allPublicConstructors": true } ]
也可以添加Jackson官方的GraalVM支持依赖com.fasterxml.jackson.module:jackson-module-graal,它会自动生成所需的原生镜像配置。
4. 手动构建ConcurrentHashMap
绕过Jackson的反射转换逻辑,直接遍历JsonNode手动构建对象,彻底避免反射依赖问题:
if (v != null && v.isObject()) { accounts = new ConcurrentHashMap<>(); ObjectMapper mapper = (ObjectMapper) jp.getCodec(); Iterator<Map.Entry<String, JsonNode>> fields = v.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); Integer key = Integer.valueOf(entry.getKey()); Account account = mapper.convertValue(entry.getValue(), Account.class); accounts.put(key, account); } }
问题原因
GraalVM原生镜像是静态编译,会执行死代码消除和反射调用分析。Jackson在运行时通过反射创建ConcurrentHashMap,但GraalVM的静态分析未检测到该反射路径,因此未生成对应的构造器元数据,导致运行时无法找到构造方法。而JVM环境是动态执行,反射可以正常访问类的构造器。
内容的提问来源于stack exchange,提问作者zipper

