吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 525|回复: 16
收起左侧

[讨论] 记一次类加载的问题

[复制链接]
xuzhenkang 发表于 2023-11-14 23:00

就是今天发生的事情。

一个应届生写了一个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包中的那两个类,但是没有走他在项目中重写的那两个类。

大神们,你们知道这是为什么吗?你们知道该如何解决吗?
(如果需要细节,随时留言,我可以补充。)

当然,这是一个已经有答案的案例哦。后面我会把答案公布出来。^~^

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
你好早安啊 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

1124480274 发表于 2023-11-15 09:29
是不是包扫描没扫到他重写的两个类
Qvv1koa 发表于 2023-11-15 09:33
同包名同类名,已经加载过一回了,类加载器在去扫描的时候,重写的没有被扫描到吧
Qvv1koa 发表于 2023-11-15 09:34
q13940240939 发表于 2023-11-15 09:33
同包名同类名,已经加载过一回了,类加载器在去扫描的时候,重写的没有被扫描到吧

类加载器加载数据是根据类的全限定名去找的,自己重写后,如果类的全限定名一致的话,不会报错,但是走到那个 全凭运气了
backaxe 发表于 2023-11-15 09:46
如果项目中包含了多个版本的同名类,类加载器通常会加载它首先找到的那个版本。如果重写的类没有被加载,很可能是因为类加载器首先加载了JAR包中的原始类。
 楼主| xuzhenkang 发表于 2023-11-15 22:07
1124480274 发表于 2023-11-15 09:29
是不是包扫描没扫到他重写的两个类

刚开始,我也是这么认为的,添加了扫描包的代码,在启动类上,单独添加了 修改的druid对应的源码包,也同样搞不出。
 楼主| xuzhenkang 发表于 2023-11-15 22:20
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,一定是会被加载到的。
 楼主| xuzhenkang 发表于 2023-11-15 22:31

书接上文。

我们做了很多尝试。原本考虑要不要把 maven 中引用druid的部分改下,看看能不能把那两个重写的类 排除掉。后来发现,maven依赖最小的管理单元是jar包,没法排除某一个类。
相关依赖是这样的:

<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-15 22:39
本帖最后由 xuzhenkang 于 2023-11-16 12:43 编辑

后来我慢慢研读项目代码。发现了一个重要的问题。
项目结构是父子模块型的。
他写的这块属于core层代码,引用druid的依赖放入了core子模块中。然后我看到项目中还有一处引用了druid依赖(也是1.2.8版本),在另一个子模块中,这个子模块叫query。
另外,查看依赖关系了解到,query模块是依赖core模块的,所以,在query中引用的druid依赖,是完全不需要的,这里又引用了一次,会将core引用的依赖,以及core中编写的源码,覆盖掉。。。。。
所以,始终无论怎样单步调试没有跳入core中重写的源码中。。。。
就这样,把query中引用的druid依赖去掉,项目正常的跑起来了


-----
感谢 大佬 @1124480274  的提示。
 楼主| xuzhenkang 发表于 2023-11-15 23:01
复习一下,类加载过程中的“双亲委托模式”。

双亲委托模式图解

双亲委托模式图解
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 18:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表