Java 有否 在不预配置类路径类名的情况下,运行态动态装载类及其依赖类的方法? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
matepi
V2EX    Java

Java 有否 在不预配置类路径类名的情况下,运行态动态装载类及其依赖类的方法?

  •  
  •   matepi 2023-02-06 14:32:45 +08:00 2303 次点击
    这是一个创建于 987 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求:运行态、不重启,动态装载本地 /远程类。

    SPI 带类路径类名配置的比较容易做到。OSGi 是不是也类似的搞法?

    但如果要求不配置的情况下,此时如果只是装载单个无依赖类(严格说是依赖均已存在的类),也比较容易做到。如果要进一步装载这个无依赖子类的父类啥的,想办法搞搞也能做到。

    但难的就是,如何把这个类的所有依赖也装进来。

    想来想去,想到两个办法:

    1 、要么就是装载类里面自己先搞个成员,自声明需要依赖的类(但这个就要保证对所有的依赖、依赖的依赖……都要声明出来、挺难保证的);

    2 、还有一种就是给个保证 100%覆盖率的对应 Tester 方法或者类内方法,预先加载 Tester 方法,循环处理掉每个 ClassNotFound 异常,做对应依赖的装载(但这个要保证 100%覆盖率、甚至是依赖类的覆盖率也要做到,也是挺容易出问题的)

    第 1 条附言    2023-02-07 09:42:41 +08:00
    更新一下这个问题的目前解决思路、非最优:

    妥协在非最优的先实现了的原因
    如果在 jar 包内部通过 Class.forName 、或者自定义 ClassLoader 方法去装载类,这样的类装载肯定是要到运行态才能发现的因此配置不可避免,和大家说的一样。

    那么即便是非最优、但提的上 [有点优] ,那就还是要搞点事情的:
    1 、可只配置依赖 jar 包文件路径,而不必明确每个需要依赖的类名
    2 、当出现依赖不完整时,即出现 ClassNotFound/NoSuchMethodException 异常时,自动尝试寻找更进一步的企业内框架 jar 库路径自动解决依赖,作为托底,并对应 warning log 提示动态装载类开发者明确依赖(这里有点安全性问题、企业内应该还好)

    先阅读背景材料:
    https://stackoverflow.com/questions/60764/how-to-load-jar-files-dynamically-at-runtime
    然后跟着写了一个代码,没有发现所说的 java<=8 和>=9 两者存在区别,用 URLClassLoader 足以解决问题

    [但 这 里 有 个 大 大 大 坑]
    URLClassLoader 是一个实现 Closeable 的 resource ,IDE 会提示你 close ,否则一直 warning
    如果你 close 了,那么恭[进]喜[坑]

    例:
    你在 A.class 里面动态加载 B.class ,在需调用计算的 B.someCaller 里面,引用了一个 C.jar 里面的 D.class 的 D.someCallee

    伪码:
    myClsLoader = new URLClassLoader(someClsPathURL, someDefaultLoader)
    Class<?> bClass = myClsLoader.loadClass(BclassName)
    myClsLoader.close()
    return bClass;
    bClass 返回的很好,close 正常,啥错都没有。然后再进一步调用
    bInstance = bClass.newInstance()
    也没问题,但
    bInstance.someCaller()
    此时,就会报错 ClassNotFound: D.class
    摸不着头脑了吧,一开始还以为是 java<=8 和>=9 的区别,搞了一圈 hack ,结果实际是

    [这个 URLClassLoader 是在真正的 Call 没有发生前,是不能关闭的;关了就没办法加载进一步 Call 内引用的 C.jar 内依赖 D.class]
    这样 URLClassLoader 的生命周期就要和 Caller 的生命周期一样了,从加载框架层、跨越到逻辑执行层,味道太烂了。不能关。

    那不关是不是就 resource leak 了呢?单纯这么写,当然是 leak 的
    参: https://blog.csdn.net/moneyshi/article/details/81939477 ,但按这文章里面讲的办法关闭估计还是无法加载 D.class 的

    那么怎么解决 leak 呢?
    有不能关闭的 resource ,把它 cache 起来,保证 resource load once and only once ,这样不就行了嘛。同时也能 cache 各种 loader 过程和反射方法等等,一举多得。

    但是进一步的又有点 [担心] :
    a ,被 cache 的 URLClassLoader 是否会在对象回收时,被 JVM 主动释放,造成 cache stale 现象
    b ,毕竟是个外部 resource ,是否会影响 JVM 的优雅停止

    因此在 [有点优] 的事情 2 上,本身就是要 try 包住 caller 过程的,其中考虑用户依赖不完整时,同时也处理掉 staled-cache ,重建 URLClassLoader 重新 cache 。不过担心 b 还是存在啊

    这样一路实现下来,感觉 [有点优] 里不优雅的事情还是很多啊……大家有什么更好的设计思路?
    16 条回复    2023-02-14 10:46:22 +08:00
    assiadamo
        1
    assiadamo  
       2023-02-06 14:35:52 +08:00
    agent + instrument
    matepi
        2
    matepi  
    OP
       2023-02-06 14:59:58 +08:00
    @assiadamo 你说的应该是 instrumentation.appendToSystemClassLoaderSearch 之类的吧?这个也就是 premain 一次性的。进一步的还是要解决问题 1 吧?
    assiadamo
        3
    assiadamo  
       2023-02-06 15:08:32 +08:00
    嗯,这只是临时热更解决问题,之后还是需要带上改动出版本更新维护的
    assiadamo
        4
    assiadamo  
       2023-02-06 15:10:59 +08:00
    运行时可以通过 rmi 或 jmx ,去调用 instrumentation 的 api 去替换 class 的字节码
    matepi
        5
    matepi  
    OP
       2023-02-06 15:31:42 +08:00
    @assiadamo 我的需求不是临时热更新处理一些临时问题。而是功能特性上,在一些处理本地文件分析的节点、在设计上,就要能够动态加载 节点使用者所提交的类。由于处理文件量很大,设计上必须是把处理逻辑分发到每个节点上,而不是把文件整体提交到专有的处理节点。
    registerrr
        6
    registerrr  
       2023-02-06 15:42:09 +08:00
    @matepi 按你这描述,不如让节点使用者单独起一个服务并保证其可用性,让你的服务去调用它,而不是把它化成你的一部分。
    aguesuka
        7
    aguesuka  
       2023-02-06 15:46:07 +08:00
    osgi 也是配置 require-bundle 才能实现的. 也就是加载整个模块, 并且在模块的 MANIFEST 里指定依赖.
    janwarlen
        8
    janwarlen  
       2023-02-06 15:55:52 +08:00
    应该不行的吧,java 的类路径相当于 class 的 id 了
    可以尝试将类打入 jre ,然后不用 package ,扁平化存放
    jdk9 的 jlink 可以自定义 jre 的

    不过还不知道有人这么干过,如果成功了踢我一脚
    matepi
        9
    matepi  
    OP
       2023-02-06 16:18:52 +08:00
    @registerrr 上面回复中也提到了:调用的入参是一个大文件,做的功能大文件的数据处理;如果变为远程调用、以服务方式 把大文件整体传输过去,效率上不能行。对大文件此时的设计,需要是分发计算,而不是分发数据。
    2han9wen71an
        10
    2han9wen71an  
       2023-02-06 17:29:17 +08:00
    你可能需要的是 URLClassLoader
    assiadamo
        11
    assiadamo  
       2023-02-06 19:06:49 +08:00 via Android
    为了实现不停机的需求的话,可不可以使用脚本,比如 Javascript 或 Lua
    pursuer
        12
    pursuer  
       2023-02-06 19:07:57 +08:00
    如果只要求标准 jre 平台,可以重载 classloader 的 findClass ,调用 defineClass 就行。
    如果要 android 平台则需要返回一个由另一个子 classloader 加载的对应类。
    第二种方法我之前开发一个框架的时候写过类似的东西,可以参考一下
    https://github.com/partic2/xplatj/blob/main/commonj/src/main/java/xplatj/javaplat/pursuer/lang/IntegratedClassLoader.java
    aristotll
        13
    aristotll  
       2023-02-06 22:12:08 +08:00
    Groovy 脚本
    ql562482472
        14
    ql562482472  
       2023-02-07 09:35:59 +08:00
    我觉得你可以写自己的类加载器,当被加载时,如果符合某些特征就进行初始化,否则就光加载连接
    nekoneko
        15
    nekoneko  
       2023-02-07 21:07:19 +08:00
    Groovy
    Aresxue
        16
    Aresxue  
       2023-02-14 10:46:22 +08:00
    动态装载的类也是有它的开发环境和过程的,可以考虑在开发过程中把这个类使用的类打包为一个新的 jar ,装载时使用自定义的 classloader 去 load 这个 jar ,至于实际实现的话可以用静态分析( import 的递归和对反射的分析)和动态分析(限定类的指定运行方法,开发时自己 run 一遍将 jvm 中所有类都记下来,将 jdk 和已有依赖的类排除其它的类合并为一个新的 jar ),这东西简单想想还行实际做起来确实挺复杂
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5361 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 05:55 PVG 13:55 LAX 22:55 JFK 01:55
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86