I've 3 class loaders:
There's only one instance of MainLoader throughout execution of a program, but PreloadingLoader and GameSceneLoader can be recreated on demand.
When I load any class in my program, I want to:
staticClasses
load it with MainLoaderpreloadingClasses
load it with PreloadingLoaderpl.gieted.flappy_bird
but isn't listed on neither of above lists, load it with GameSceneLoaderThis below code works, but only for the first class loaded, e.g:
pl.gieted.flappy_bird.engine.Renderer
is requested by GameSceneLoaderRenderer
has a class dependency of LoadingScene
Renderer
was loaded using MainLoader, the Loading Scene
is also being loaded using MainLoader, however it can't find it.java.lang.NoClassDefFoundError
is thrown.What I want to happen instead is:
pl.gieted.flappy_bird.engine.Renderer
is requested by GameSceneLoaderRenderer
has a class dependency of LoadingScene
LoadingScene
is passed back to GameSceneLoaderval 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")
The examples are written in Kotlin, however you can answer me in Java without any problems.
I've ended up with creating such class loader like this:
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()
}
}
The whole trick is creating an extra actuallyLoad()
function, that actually loads the class and delegating all loadClass()
calls back to your "router".