先说谜底就没意思了

很有意思 - 一个排查的过程, 如果先给出谜底就没意思了. 跟魔术, 悬疑小说是一样的. 如果想破坏阅读体验, 上来就说”单词拼错啦”就完事了.

所以能把技术文章写得妙趣横生的, 都是从一开始的异常症状开始说, 徐徐道来, 波澜起伏, 曲径通幽. 具备这样定力的人, 就能写出有阅读乐趣的文章, 可是这和工程师的直觉似乎非常违背 - 这么爱废话, 代码能写得快吗? 所以这些工程师+作者真是适合自媒体时代呀.

闲言少叙, 这次的直接表现是, mybatis从DB取数据, deserialize成entity class的时候, 其他类型的field还原得还行, 惟有List类型 (array没试, 实体类里没有String[]) 还原回来是个null.

第一个怀疑, 这个东西好过吗? 是碰坏了还是一直这样? 大概率之前是好的. 那么改动太多了, 碰了啥呢.

开始step吧! 一上来大量ibatis/mybatis的堆栈, stmt, cache, session, connection, 多个resultSet join什么的. 终于到了比较有用的mapper部分. 看看自己的mapper生效没有, 答案是肯定的.

然后就到了自己的实体类相关的代码. 这里, 我们的实体统一继承一个SerFieldMap类, 里面每个字段初始化SerFieldNode, 来实现一些统一的功能. 刚好我们在查的实体外层有50来个field, 内层60来个… 然后写的conditional break point发现常常不生效, 应该是Intellij的问题, 实在无从解决. 其实如果知道流程会这么冗长, 编个1 2个字段的实体也许更方便一些.

不管怎么样, 继续走了进去. 发现还原的地方, 首先检查field是不是基本类型, 如果是直接赋值. 否则的话有3个可能, GENERIC_ARRAY, PARAMETERIZED_TYPE, CLASS, 最后如果什么都不是就没办法赋值null了. 其中, CLASS是一般一切类的处理, PARAMETERIZED_TYPE用于带generic类型的一切, 由于类型擦除, 要从runtime想办法捞出来然后试图实例化里面那个类. GENERIC_ARRAY就是形如String[]的情况.

List肯定应该是进PARAMETERIZED_TYPE的, 但是都没进, 给了null. 为什么呢? 研究一下, 终于发现了: 比较是这样做的 `(list instanceof ParameterizedTypeImpl)`. 这个地方, 我们代码是没改, 但是有个改动是*从JDK1.8换到了JDK11*. JDK11不再export一些来自java.base/sun.reflect的类, 虽然运行时检查的时候getClass().getCanonicalName()能看出还是这些类. 所以, 当时看到import报错, 了解sun的ParameterizedTypeImpl没了, 自然就想用其他实现同个接口的实现类来替换, 就用了google的. 解决方式是有道理的, 但关键是instanceof检查过不了了呀!

这是搞清楚root cause了, 想方案吧. 方法很多, 但是还希望在框架内解决问题. 后来想让JDK11想想办法export这些类吧, 给javac加了参数(gradle compileJava task传下去) --add-exports java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED. export倒是有了, 会抛UnsupportedOperation. 总之, 真的不想让你再用这些类了. 然后想能不能把这几个类捞出来, 覆盖一下. 发现覆盖的类确实不生效, 应该是JDK里面的类是特殊的classloader, 不会从你这里search/走. 何况这几个impl每个也有自己的依赖, 那些类也需要捞出来, 子子孙孙…

终于, 又到了喜闻乐见的”the hard way is the only way”环节. 把实现类impl替换成允许使用的interface类, 要求必须传Class<?>的地方就.getClass()来满足, 然后在暴露出的有限方法里 (或者用反射?) 想办法完成赋值. 一开始改得还挺顺利, 走着走着, 走不通了. 发现是到transValue这里, 对类的检查似乎不对, 没有处理还是null了. 这个地方堪称最困惑的部分… 首先花了好多时间想还原原本的behavior来参考, 掺杂新包旧包不同repo不同jar版本发布是否成功是否覆盖是否重新拉依赖问题, 很麻烦浪费时间若干…

终于终于, 谜底揭晓, transValue有多个overloading. 有的overloading直接就是用的ParameterizedTypeImpl作为类型. 那现在改成了ParameterizedType, 这是OK的. 但是怎么都.getClass()了? 这样就都进了给Class准备的overloading, 而在那个overloaded impl里面, 也会检查是不是collection, 是不是array, 等等, 所以迷惑性很大. 原来如此.

最后, 主要就是用GenericArrayType ParameterizedType WildcardType这几个interface来替换impl, 然后getRawType之后cast成Class<?> (因为到这里了肯定是Class) 解决了问题.

Written on April 30, 2020