You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建Parent-Last(父类最后)类加载器层级并实现自定义加载规则

问题根因

Java默认双亲委派模型中,类的依赖默认由定义该类的类加载器负责发起加载。原有逻辑里MainLoader加载了Renderer类后,Renderer的依赖LoadingScene会由MainLoader直接发起加载,而MainLoader没有下游加载器的引用,无法转发请求,就会抛出NoClassDefFoundError。

解决方案

核心思路是在MainLoader中增加向下转发的逻辑,将不属于自己负责的业务包类,转发给当前发起加载请求的下游加载器处理,这里借助JDK自带的线程上下文类加载器传递下游加载器实例。

修改后的代码

import java.io.File
import java.net.URLClassLoader

// 全局缓存系统类加载器,避免重复获取
private val systemClassLoader = ClassLoader.getSystemClassLoader()

val mainClassLoader = object : URLClassLoader(arrayOf(File(classesUrl).toURI().toURL()), null) {

    val staticClasses = listOf(
        "pl.gieted.flappy_bird.engine.Renderer",
        "pl.gieted.flappy_bird.engine.Processing",
        "pl.gieted.flappy_bird.engine.Scene",
        "pl.gieted.flappy_bird.engine.LifecycleElement",
    )

    override fun loadClass(name: String, resolve: Boolean): Class<*> {
        synchronized(getClassLoadingLock(name)) {
            // 先检查类是否已经加载过
            var loadedClass = findLoadedClass(name)
            if (loadedClass != null) {
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 系统类优先交给系统类加载器加载,避免破坏JDK基础逻辑
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("sun.") 
                || name.startsWith("jdk.") || name.startsWith("org.w3c.") || name.startsWith("org.xml.")) {
                loadedClass = systemClassLoader.loadClass(name)
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 属于staticClasses的类,当前加载器自行加载
            if (staticClasses.any { name.startsWith(it) }) {
                loadedClass = findClass(name)
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 属于业务包但不属于自己负责的类,转发给当前线程上下文类加载器
            if (name.startsWith("pl.gieted.flappy_bird")) {
                val contextClassLoader = Thread.currentThread().contextClassLoader
                // 避免当前加载器就是上下文加载器导致死循环
                if (contextClassLoader != this) {
                    loadedClass = contextClassLoader.loadClass(name)
                    if (resolve) resolveClass(loadedClass)
                    return loadedClass
                }
                throw ClassNotFoundException(name)
            }

            // 其他非业务类,交给系统类加载器
            loadedClass = systemClassLoader.loadClass(name)
            if (resolve) resolveClass(loadedClass)
            return loadedClass
        }
    }
}

var preloadingLoader = object : URLClassLoader(arrayOf(File(classesUrl).toURI().toURL()), mainClassLoader) {

    val preloadingClasses = listOf(
        "pl.gieted.flappy_bird.game.LoadingScene",
        "pl.gieted.flappy_bird.game.FlappyBirdResourceLoader",
        "pl.gieted.flappy_bird.game.Resources",
    )

    override fun loadClass(name: String, resolve: Boolean): Class<*> {
        synchronized(getClassLoadingLock(name)) {
            var loadedClass = findLoadedClass(name)
            if (loadedClass != null) {
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 属于preloadingClasses的类,优先自行加载
            if (preloadingClasses.any { name.startsWith(it) }) {
                loadedClass = findClass(name)
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 其他类交给父加载器处理
            return super.loadClass(name, resolve)
        }
    }
}

var gameSceneLoader = object : URLClassLoader(arrayOf(File(classesUrl).toURI().toURL()), preloadingLoader) {
    override fun loadClass(name: String, resolve: Boolean): Class<*> {
        synchronized(getClassLoadingLock(name)) {
            var loadedClass = findLoadedClass(name)
            if (loadedClass != null) {
                if (resolve) resolveClass(loadedClass)
                return loadedClass
            }

            // 业务包内不属于前两个列表的类,优先自行加载
            if (name.startsWith("pl.gieted.flappy_bird")) {
                return try {
                    loadedClass = findClass(name)
                    if (resolve) resolveClass(loadedClass)
                    loadedClass
                } catch (e: ClassNotFoundException) {
                    super.loadClass(name, resolve)
                }
            }

            // 其他类交给父加载器处理
            return super.loadClass(name, resolve)
        }
    }
}

// 加载入口类时先设置线程上下文类加载器,加载完成后可按需恢复原有上下文
val rendererClass = Thread.currentThread().contextClassLoader.let { originLoader ->
    Thread.currentThread().contextClassLoader = gameSceneLoader
    val clazz = gameSceneLoader.loadClass("pl.gieted.flappy_bird.engine.Renderer")
    Thread.currentThread().contextClassLoader = originLoader
    clazz
}

流程验证

完全符合你期望的加载逻辑:

  1. 调用gameSceneLoader.loadClass加载Renderer前,先将线程上下文类加载器设为gameSceneLoader
  2. 请求走双亲委派链到MainLoader,MainLoader识别到Renderer属于staticClasses,自行完成加载
  3. 解析Renderer依赖的LoadingScene时,调用MainLoader的loadClass方法
  4. MainLoader发现LoadingScene不属于自己负责的范围,转发给当前线程上下文的gameSceneLoader处理
  5. gameSceneLoader走双亲委派到MainLoader,MainLoader不处理,回传给preloadingLoader
  6. preloadingLoader识别到LoadingScene属于preloadingClasses,自行完成加载
  7. 整个流程无异常完成

注意事项

多线程场景下加载业务类时,需要确保对应线程的上下文类加载器设置正确,避免转发失败。需要重建PreloadingLoader、GameSceneLoader时直接替换实例即可,只要没有静态变量持有这两个加载器加载的类实例,就可以正常卸载旧加载器。

内容的提问来源于stack exchange,提问作者Gieted

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 17:48:00