首页 > 解决方案 > 如何创建父级最后的类加载器层次结构?

问题描述

我有 3 个类加载器:

  1. 主加载器
  2. 预加载加载器
  3. 游戏场景加载器

在整个程序执行过程中只有一个MainLoader实例,但PreloadingLoaderGameSceneLoader可以按需重新创建。

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


下面的代码有效,但仅适用于加载的第一个类,例如:

  1. pl.gieted.flappy_bird.engine.RendererGameSceneLoader请求
  2. MainLoader尝试加载它,因为它是GameSceneLoader最古老的父级
  3. Renderer一个类依赖LoadingScene
  4. 由于Renderer是使用MainLoader加载的,因此Loading Scene也正在使用MainLoader加载,但是找不到它。
  5. java.lang.NoClassDefFoundError被抛出。

我想要发生的是:

  1. pl.gieted.flappy_bird.engine.RendererGameSceneLoader请求
  2. MainLoader尝试加载它,因为它是GameSceneLoader最古老的父级
  3. Renderer一个类依赖LoadingScene
  4. 的加载LoadingScene被传回GameSceneLoader
  5. MainLoader找不到它。
  6. PreloadingLoader找到并加载它
  7. 加载继续...
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 回答我。

标签: javaclassloaderdynamic-class-loadersclassloading

解决方案


我最终创建了这样的类加载器:

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()函数,它实际上加载类并将所有loadClass()调用委托给您的“路由器”。


推荐阅读