记一次类加载的问题
就是今天发生的事情。一个应届生写了一个sql转换的功能,是基于开源组件druid springboot starter 1.2.20版本,重写了jar包中的两个类,放到了项目工程中(同包名,同类名)。项目跑起来完全没问题。经过验证,功能表现也正常。但是我们需要把项目部署到云上时,发现无法找到1.2.20版本的druid依赖库,研究了一下,是由于公司内的仓库中,没有这个版本的依赖。没办法就将druid版本降低至1.2.8,.这个版本在仓库中是有的。(公司限制较多,引用开源组件的时候要走很长很长很长的审批流程,存在各种安全性评估。因此放弃了1.2.20版本。)
然后这位小同学就有点慌张,因为降低了druid版本,意味着他前期做的工作几乎全都无效了。但是,聪明的他又怎会放弃曾经熬夜得出的劳动成果呢?他是这样做的:
1. 他将原来使用1.2.20版本重写的两个类,放进了新的工程中(即降低了druid版本的工程),然后发现有若干报错。很显然,基于高版本修改的druid版本源码中肯定会有些特性是低版本不存在的,所以,他在druid1.2.20的源码中找到了相关的代码,也同样的,原封不动的放入工程中(同包名,同类名)。
2. 恩恩,经过他的不懈努力,找了一个又一个,终于不报错了,项目可以跑起来,然后草草的测了一下,就这样,代码push了。
3. 我和另一位大哥,在很忙的情况下需要对他提交的pull request进行review,通过后会合并到dev分支上,然后走流水线发布到云环境上。这就是我们“常规”的流程。但是今天很不同,我和那位大哥都忙极了!匆匆的将他提交的代码通过了,就这样,他的代码上了云~~~~
4. 你可能想象到了,大事不妙,确实,出问题了。现象是,他重写的那部分代码——基于druid1.2.20修改的那两个类,没有被执行到。功能出现了异常。
5. 我们在开发环境下(本地idea环境)也复现了这个问题,代码走入了jar包中的那两个类,但是没有走他在项目中重写的那两个类。
大神们,你们知道这是为什么吗?你们知道该如何解决吗?
(如果需要细节,随时留言,我可以补充。)
当然,这是一个已经有答案的案例哦。后面我会把答案公布出来。^~^ 是不是包扫描没扫到他重写的两个类 同包名同类名,已经加载过一回了,类加载器在去扫描的时候,重写的没有被扫描到吧 q13940240939 发表于 2023-11-15 09:33
同包名同类名,已经加载过一回了,类加载器在去扫描的时候,重写的没有被扫描到吧
类加载器加载数据是根据类的全限定名去找的,自己重写后,如果类的全限定名一致的话,不会报错,但是走到那个 全凭运气了 如果项目中包含了多个版本的同名类,类加载器通常会加载它首先找到的那个版本。如果重写的类没有被加载,很可能是因为类加载器首先加载了JAR包中的原始类。 1124480274 发表于 2023-11-15 09:29
是不是包扫描没扫到他重写的两个类
刚开始,我也是这么认为的,添加了扫描包的代码,在启动类上,单独添加了 修改的druid对应的源码包,也同样搞不出。 q13940240939 发表于 2023-11-15 09:33
同包名同类名,已经加载过一回了,类加载器在去扫描的时候,重写的没有被扫描到吧
在Java项目中,类加载的顺序也是有优先级的,一般优先级如下:
1. $JAVA_HOME/lib 目录下的java核心api
2. $JAVA_HOME/lib/ext 目录下的java扩展jar包
3. java -classpath/-Djava.class.path所指的目录下的类与jar包
4. $CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载
5. $CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载
6. $CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载
7. 我们的项目路径/WEB-INF/classes下的class文件
8. 我们的项目路径/WEB-INF/lib下的jar文件
同一个类加载器实例加载的class不允许重复,这里重复的定义是:包全名+类名(不同的class文件,同样的类名也是重复)。
java -classpath(-cp)加载配置jar包或classes时,会按照定义顺序加载class,之后重复加载的class会被忽略,只有首个生效。
其实 jar包中的class优先级不如项目中编写的class优先级高,正常情况下,项目中重写的class,一定是会被加载到的。
书接上文。
我们做了很多尝试。原本考虑要不要把 maven 中引用druid的部分改下,看看能不能把那两个重写的类 排除掉。后来发现,maven依赖最小的管理单元是jar包,没法排除某一个类。
相关依赖是这样的:
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
```
然后,我意识到,这位小同学,忽略了一件事:他为了避免曾经熬夜取得的劳动成果付诸东流,就直接把基于druid1.2.20修改的源码放入到了druid1.2.8版本的项目中了,然后增加了一些依赖的类。那么,会不会是基于1.2.20版本修改的源码与1.2.8版本的类有什么不同,导致这两个类都被加载了,只不过是项目运行时,调用到的是1.2.8中的类呢?我让他基于druid1.2.8的源码,将之前的特性修改了一遍(其实就是增加了两个方法,增加了两个if判断)。
经过他1个多小时的努力,修改完了。
然后,您猜怎么着?——诶,还是失效的。。。。 本帖最后由 xuzhenkang 于 2023-11-16 12:43 编辑
后来我慢慢研读项目代码。发现了一个重要的问题。
项目结构是父子模块型的。
他写的这块属于core层代码,引用druid的依赖放入了core子模块中。然后我看到项目中还有一处引用了druid依赖(也是1.2.8版本),在另一个子模块中,这个子模块叫query。
另外,查看依赖关系了解到,query模块是依赖core模块的,所以,在query中引用的druid依赖,是完全不需要的,这里又引用了一次,会将core引用的依赖,以及core中编写的源码,覆盖掉。。。。。
所以,始终无论怎样单步调试没有跳入core中重写的源码中。。。。
就这样,把query中引用的druid依赖去掉,项目正常的跑起来了
-----
感谢 大佬 @1124480274的提示。{:1_893:} 复习一下,类加载过程中的“双亲委托模式”。
页:
[1]
2