背景
A 组件依赖了 jackson-module-kotlin 2.16.0 版本组件. 项目依赖了 A 组件, 但因为 A 组件的 jackson-module-kotlin 因为兼容问题, 没法用太高的版本, 导致 jackson-module-kotlin 需要降级. 因此在项目里面修改了的 jackson 的组件版本, 降成了2.10. 然后就出现下面的现象.
java
Caused by:java.Lang.NoSuchMethodError Create breakpoint com.fasterxml.jackson.module.kotlin.KotlinModule.<init>(IZZZLcom/fasterxml/jackson/module/kotlin/SingletonSupport;
ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
at com.pinco.plugin.common.serlallzer.ob]ectMapperFactory$companion.buildob]ectMapper(ob]ectMapperFactory.kt:34)
at com.pinco.plugin.common.serializer.ObjectMapperFactory$Companion.accesssbuildobjectMapper(objectMapperFactory.kt:20)
at com.pinco.plugin.common.serializer.objectMapperFactory.<clinit>(objectMapperFactory.kt:21)
at ardServiceImpl.kt:121)
看了一下代码, 报错的地方在这里, 查看源码没发现有编译的问题.
java
val objectMapper = ObjectMapper()
.registerModule(
KotlinModule() // 这里抛出异常
)
初步猜想
是 jackson 2.10 版本, 对kotlin还是不友好? 因为项目的 kotlin 版本是 1.3, 怀疑是因为和 kotlin 版本不兼容导致的. 因为 jackson-module-kotlin 是 jackson 的其中一个模块, 版本要一起更新才不会有兼容问题. 因此在项目里面把 jackson 降到2.5, 还是有这个问题, 低于 2.5 版本的 kotlin 版本太高, 用 2.16 jackson 提示 kotlin版本太高, 怎么改都有异常. 最后某次无意间在 A组件 改了 jackson 的版本, 改成 2.10, 竟然没报错了. 但在 A 组件改jackson版本实际上对项目不会有影响的, 因为 A组件已经强制指定了 jackson 版本. 那为什么改了 A组件的版本就没问题的呢?
分析异常原因
根源还是在异常提示那里, noSuchMethodError的方法有点奇怪, KotlinModule 看调用是一个无参的构造方法, 为什么会提示有一堆的参数呢? (IZZZLcom/fasterxml/jackson/module/kotlin/SingletonSupport; ZZZILkotlin/jvm/internal/DefaultConstructorMarker;). 这是因为 KotlinModule 并非有无参构造函数, 而是默认参数隐藏了入参.
A组件 分别用2.16.0和 2.10.0 打包, 反编译, 发现两个版本打包出来的KotlinModule() 是不一样的.
java
// 2.10.0 版本
ObjectMapper objectMapper = (new ObjectMapper()).registerModule(
(Module)(new KotlinModule(0, false, false, 7, (DefaultConstructorMarker)null))
);
// 2.16.0 版本
ObjectMapper objectMapper = (new ObjectMapper()).registerModule(
(Module)(new KotlinModule(0, false, false, false, (SingletonSupport)null, false, false, false, 255, (DefaultConstructorMarker)null))
);
虽然看上去没有编译错误, 但实际依赖 jackson 2.16.0 的 A组件上在运行过程会new 11个参数的构造方法. 而项目里面依赖的是 2.10.0, 只有 5个参数的构造方法, 因此会提示找不到 11个参数的构造方法.
结论
这其实是 kotlin 对默认参数的实现导致的. kotlin 底层是字节码, 字节码层面并没有提供对默认参数的实现, 因此kotlin 只能通过编译来支持新特性.
KotlinModule 因为用了默认参数, 会导致不同版本打包出来的字节码会不一样, 因此会导致兼容的问题. 例如 组件A使用了 2.16.0 打包, 并使用了默认方法 项目就只要能用 jackson-module-kotlin 2.16.0, 改了 jackson 版本即使看上去编译没问题, 也可能运行不了.