V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
matepi
V2EX  ›  Java

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

  •  
  •   matepi · Feb 6, 2023 · 2656 views
    This topic created in 1175 days ago, the information mentioned may be changed or developed.

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

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

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

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

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

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

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

    Supplement 1  ·  Feb 7, 2023
    更新一下这个问题的目前解决思路、非最优:

    妥协在非最优的先实现了的原因——
    如果在 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 replies    2023-02-14 10:46:22 +08:00
    badbye
        1
    badbye  
       Feb 6, 2023
    agent + instrument
    matepi
        2
    matepi  
    OP
       Feb 6, 2023
    @assiadamo 你说的应该是 instrumentation.appendToSystemClassLoaderSearch 之类的吧?这个也就是 premain 一次性的。进一步的还是要解决问题 1 吧?
    badbye
        3
    badbye  
       Feb 6, 2023
    嗯,这只是临时热更解决问题,之后还是需要带上改动出版本更新维护的
    badbye
        4
    badbye  
       Feb 6, 2023
    运行时可以通过 rmi 或 jmx ,去调用 instrumentation 的 api 去替换 class 的字节码
    matepi
        5
    matepi  
    OP
       Feb 6, 2023
    @assiadamo 我的需求不是临时热更新处理一些临时问题。而是功能特性上,在一些处理本地文件分析的节点、在设计上,就要能够动态加载 节点使用者所提交的类。由于处理文件量很大,设计上必须是把处理逻辑分发到每个节点上,而不是把文件整体提交到专有的处理节点。
    registerrr
        6
    registerrr  
       Feb 6, 2023
    @matepi 按你这描述,不如让节点使用者单独起一个服务并保证其可用性,让你的服务去调用它,而不是把它化成你的一部分。
    aguesuka
        7
    aguesuka  
       Feb 6, 2023
    osgi 也是配置 require-bundle 才能实现的. 也就是加载整个模块, 并且在模块的 MANIFEST 里指定依赖.
    vvvVictoria
        8
    vvvVictoria  
       Feb 6, 2023
    应该不行的吧,java 的类路径相当于 class 的 id 了
    可以尝试将类打入 jre ,然后不用 package ,扁平化存放
    jdk9 的 jlink 可以自定义 jre 的

    不过还不知道有人这么干过,如果成功了踢我一脚
    matepi
        9
    matepi  
    OP
       Feb 6, 2023
    @registerrr 上面回复中也提到了:调用的入参是一个大文件,做的功能大文件的数据处理;如果变为远程调用、以服务方式 把大文件整体传输过去,效率上不能行。对大文件此时的设计,需要是分发计算,而不是分发数据。
    2han9wen71an
        10
    2han9wen71an  
       Feb 6, 2023
    你可能需要的是 URLClassLoader
    badbye
        11
    badbye  
       Feb 6, 2023 via Android
    为了实现不停机的需求的话,可不可以使用脚本,比如 JavaScript 或 Lua
    pursuer
        12
    pursuer  
       Feb 6, 2023
    如果只要求标准 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  
       Feb 6, 2023
    Groovy 脚本
    ql562482472
        14
    ql562482472  
       Feb 7, 2023
    我觉得你可以写自己的类加载器,当被加载时,如果符合某些特征就进行初始化,否则就光加载连接
    nekoneko
        15
    nekoneko  
       Feb 7, 2023
    Groovy
    Aresxue
        16
    Aresxue  
       Feb 14, 2023
    动态装载的类也是有它的开发环境和过程的,可以考虑在开发过程中把这个类使用的类打包为一个新的 jar ,装载时使用自定义的 classloader 去 load 这个 jar ,至于实际实现的话可以用静态分析( import 的递归和对反射的分析)和动态分析(限定类的指定运行方法,开发时自己 run 一遍将 jvm 中所有类都记下来,将 jdk 和已有依赖的类排除其它的类合并为一个新的 jar ),这东西简单想想还行实际做起来确实挺复杂
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5801 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 65ms · UTC 06:03 · PVG 14:03 · LAX 23:03 · JFK 02:03
    ♥ Do have faith in what you're doing.