如何创建父最后类加载器层次结构?

How to create a parent-last classloader hierarchy?

我有 3 个 class 装载机:

  1. 主加载程序
  2. 预加载加载器
  3. GameSceneLoader

程序执行过程中只有一个MainLoader实例,但是PreloadingLoaderGameSceneLoader 可以根据需要重新创建。

当我在我的程序中加载任何 class 时,我想:


以下代码有效,但仅适用于第一个 class 加载,例如:

  1. pl.gieted.flappy_bird.engine.RendererGameSceneLoader
  2. 请求
  3. MainLoader 尝试加载它,因为它是 GameSceneLoader
  4. 最旧的父级
  5. Renderer 具有 class 依赖性 LoadingScene
  6. 由于 Renderer 是使用 MainLoader 加载的,所以 Loading Scene 也是使用 MainLoader 加载的,但是它找不到它。
  7. java.lang.NoClassDefFoundError 被抛出。

我想要的是:

  1. pl.gieted.flappy_bird.engine.RendererGameSceneLoader
  2. 请求
  3. MainLoader 尝试加载它,因为它是 GameSceneLoader
  4. 最旧的父级
  5. Renderer 具有 class 依赖性 LoadingScene
  6. LoadingScene 的加载被传递回 GameSceneLoader
  7. MainLoader 找不到。
  8. PreloadingLoader找到并加载
  9. 正在继续加载...
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 findClass(name: String): Class<*>? {
        return when {
            staticClasses.any { name.startsWith(it) } -> super.findClass(name)
            name.startsWith("pl.gieted.flappy_bird") -> null
            else -> this::class.java.classLoader.loadClass(name)
        }
    }
}

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 findClass(name: String): Class<*>? {
        return when {
            preloadingClasses.any { name.startsWith(it) } -> super.findClass(name)
            else -> null
        }
    }
}

var gameSceneLoader = URLClassLoader(arrayOf(File(classesUrl).toURI().toURL()), preloadingLoader)

val rendererClass = gameSceneLoader.loadClass("pl.gieted.flappy_bird.engine.Renderer")

如何实现这样的事情?

示例是用 Kotlin 编写的,但是您可以 Java 毫无问题地回答我。

我最终创建了这样的 class 加载程序:

object MainClassLoader : ClassLoader() {
    private class MyClassLoader : URLClassLoader(
        listOf(classesUrl, resourcesUrl).map { File(it).toURI().toURL() }.toTypedArray(), null
    ) {

        override fun loadClass(name: String?, resolve: Boolean): Class<*> = MainClassLoader.loadClass(name)

        fun actuallyLoad(name: String): Class<*> = super.loadClass(name, false)
    }

    private val staticClassLoader = MyClassLoader()
    private var preloadingLoader = MyClassLoader()
    private var gameSceneLoader = MyClassLoader()

    private 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",
        
        "pl.gieted.flappy_bird.engine.Object",
        "pl.gieted.flappy_bird.engine.Vector2",
        "pl.gieted.flappy_bird.engine.Sound",
        "pl.gieted.flappy_bird.engine.Camera",
        "pl.gieted.flappy_bird.engine.Bounds",
    )

    private val preloadingClasses = listOf(
        "pl.gieted.flappy_bird.game.LoadingScene",
        "pl.gieted.flappy_bird.game.FlappyBirdResourceLoader",
        "pl.gieted.flappy_bird.game.Resources",
        "pl.gieted.flappy_bird.game.objects.Bird$Color"
    )

    override fun loadClass(name: String, resolve: Boolean): Class<*> = when {
        staticClasses.any { name.startsWith(it) } -> staticClassLoader.actuallyLoad(name)
        preloadingClasses.any { name.startsWith(it) } -> preloadingLoader.actuallyLoad(name)
        name.startsWith("pl.gieted.flappy_bird") -> gameSceneLoader.actuallyLoad(name)
        else -> MainClassLoader::class.java.classLoader.loadClass(name)
    }

    fun newPreloading() {
        preloadingLoader = MyClassLoader()
    }

    fun newGameScene() {
        gameSceneLoader = MyClassLoader()
    }
}

整个技巧是创建一个额外的 actuallyLoad() 函数,它实际上加载 class 并将所有 loadClass() 调用委托回您的“路由器”。