同名类冲突-CASE2.JDK核心类被篡改

1. 问题描述


当前项目所用技术栈:AliTomcat+Pandora+SpringMVC

进入到预发环境验证时,突然出现文件请求偶现处理失败的情况。由于在异步线程中,异常被吃掉。所以通过Arthas来观察有问题方法抛出的异常。

watch com.xxx.TestService methodName "{params,returnObj,throwExp}" -x 2

  

2. 排查思路


通过对异常堆栈信息分析,可以推测出,出现了类冲突-NoClassDefFoundError

  • 抛出的异常为NoClassDefFoundError, 表示该类在编译阶段有,JVM实例化时找不到该类,一般是类冲突导致
  • 找不到的类com.sun.xml.internal.bind.v2.model.impl.Utils 在jaxb-runtime JAR包中。
  • 类名基本都是JAXBxxx.java, 可能是该类存在冲突。从javax.xml.bind.JAXBContext入手,发现第三方JAR包重写了jdk核心类,全限定名相同的类有两个。

对比日常环境&预发环境中的同名类,发现日常是BootstrapClassLoader加载的JDK核心类,预发则是WebAppClassLoader加载了第三方JAR中的类。不同环境加载了不同JAR的类导致

  

分析到这里,存在以下疑问:

  • 为什么核心类会被篡改?跟Pandora隔离容器有关系么?因为它对传统双亲委派默认做了修改。
  • 目前部署已经docker化了,环境是一致的。是什么导致两个环境不一致呢? 
  • 为什么要重写JAVA核心类????

 

2.1 为什么核心类会被篡改?跟隔离容器有关系么?

再次确认了当前技术栈的类加载机制,如下jdk核心类会保证不被篡改。JAXBContext位于/jre/lib/rt.jar,属于JDK核心类,理应不会被篡改。具体类加载机制如下:

  • 中间件由ModuleClassLoader系列类加载器加载,应用与中间件天然的形成了隔离。
  • jdk核心类还是由BootstrapClassLoder加载,保证不被篡改

看看预发环境加载的JAXBContext并不是同名类,实际从jaxb-runtime.jar中加载的类,类路径不同。

 

2.2 为什么javax.xxx.JAXBContext实际加载了com.xxx.JAXBContext

看了下JAR包中的JAXBContext发现,这里使用了JAVA SPI机制,打破了双亲委派约定

即当加载类javax.xml.bind.JAXBContext时,通过该jar包META-INF/services/里的配置文件找到具体的实现类名,装载了com.xxx.JAXBContext。

com.sun.xml.bind.v2.ContextFactory
javax.xml.bind.JAXBContext

 

2.3 为什么日常环境、预发环境不一致?

日常环境无法回滚到当时的镜像,镜像丢弃了。 代码reset到当时的版本,重新打包镜像时发现WEB-INF/lib目录下,没有jaxb-runtime.jar包(日常、预发都没有)。

无法复现,也没法再跟进了

 

2.4 为什么要重写JDK核心类?

在部分场景下可能需要。JAVA SPI[JAVA Service Provider Interface]一个典型应用场景是java.sql.Driver。JDK只能提供接口,数据库提供商来实现

应用在加载java.sql.Driver的实现时,它会尝试找到上下文类加载器中可用的任何文件META-INF/services/java.sql.Driver,并为找到的每个文件创建该文件中定义的类的实例。

而不需要通过Class.forName("org.h2.Driver")来注册驱动。

4. 总结


  • 追查问题不要隔太久
  • JDK核心类大部分情况下由双亲委派机制保证不会被篡改,JAVA SPI机制可以打破该机制,加载自定义的实现类
  • Docker化部署理论上是环境一致的,但实际应用时,可能还是会存在细节上不一样,虽然这里还没查到具体不一致的地方

 

 

参考资料:

同名类冲突-CASE1.两个第三方JAR包中包含同名类

为什么说JAVA SPI破坏了双亲委派模型

 

posted @ 2021-02-26 10:36  大美da美  阅读(309)  评论(0)    收藏  举报