如何创建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 }
流程验证
完全符合你期望的加载逻辑:
- 调用
gameSceneLoader.loadClass加载Renderer前,先将线程上下文类加载器设为gameSceneLoader - 请求走双亲委派链到MainLoader,MainLoader识别到Renderer属于staticClasses,自行完成加载
- 解析Renderer依赖的LoadingScene时,调用MainLoader的loadClass方法
- MainLoader发现LoadingScene不属于自己负责的范围,转发给当前线程上下文的gameSceneLoader处理
- gameSceneLoader走双亲委派到MainLoader,MainLoader不处理,回传给preloadingLoader
- preloadingLoader识别到LoadingScene属于preloadingClasses,自行完成加载
- 整个流程无异常完成
注意事项
多线程场景下加载业务类时,需要确保对应线程的上下文类加载器设置正确,避免转发失败。需要重建PreloadingLoader、GameSceneLoader时直接替换实例即可,只要没有静态变量持有这两个加载器加载的类实例,就可以正常卸载旧加载器。
内容的提问来源于stack exchange,提问作者Gieted
相关产品推荐
相关产品推荐

