Java多模块项目使用ServiceLoader加载服务报非子类型错误
异常产生核心原因
该报错与LoadedRealmPlugin是否为继承自其他类的抽象类无关,本质为类加载器隔离引发的类型判定不兼容。
JVM对类的判定唯一性规则为:类的全限定名 + 加载该类的类加载器实例。同一全限定名的类如果被两个不同的类加载器分别加载,会被JVM判定为两个完全不兼容的独立类型,哪怕代码层面指向同一个接口/父类。
结合现有代码的触发链路:
主程序中LoadedRealmPlugin由项目主类加载器完成加载;自定义创建的URLClassLoader用于加载插件jar包中的SurvivalRealm实现类。当SurvivalRealm类被加载时,类加载器会根据类文件中记录的父类型全限定名查找对应的父类定义,如果自定义URLClassLoader无法从父委托链中获取主类加载器已经加载好的LoadedRealmPlugin类,就会在自身的搜索路径下加载一份新的LoadedRealmPlugin类定义。
此时SurvivalRealm实现的是「自定义URLClassLoader加载的接口版本」,而代码中传给ServiceLoader的是「主类加载器加载的接口版本」,ServiceLoader加载完实现类后会尝试将实例强转为传入的目标接口类型,由于两个版本的接口类加载器实例不同,强转必然失败,最终抛出"not a subtype"的配置错误。
常见触发场景
- 插件jar打包配置错误,将核心模块的
LoadedRealmPlugin类文件一并打入插件jar包,自定义类加载器直接从jar路径加载该类,未触发父类委托流程 - 创建
URLClassLoader时未显式指定正确的父类加载器,当项目运行在自定义类加载器环境、模块化运行环境时,默认的父委托链断裂,无法从主类加载器获取已加载的接口类 - Java 9+模块化配置错误,未在核心模块导出接口所在包,也未配置ServiceLoader的
uses声明,跨模块类加载时类型定义不可见
排查与解决步骤
- 校验插件jar包内容:直接解压
Dir/plugin_jar.jar,检查是否存在路径net/lightbluefoxlabs/dev/multirealm/core/multirealmcore/Realms/LoadedRealmPlugin.class。如果存在,调整插件模块的打包配置,将核心模块依赖设置为provided作用域,禁止将核心类打入插件包 - 修正自定义类加载器初始化逻辑:创建
URLClassLoader时,显式将加载LoadedRealmPlugin的类加载器设为父委托加载器,替换原有代码:
原有错误写法:URLClassLoader c = new URLClassLoader(new URL[]{file.getAbsoluteFile().toURI().toURL()});
修正后写法:URLClassLoader c = new URLClassLoader(new URL[]{file.getAbsoluteFile().toURI().toURL()}, LoadedRealmPlugin.class.getClassLoader());
修正后类加载流程会优先委托父加载器查找已加载的接口类,不会重复加载独立的接口版本 - 模块化项目(Java 9+)调整模块配置:在核心模块的
module-info.java中,为LoadedRealmPlugin所在包添加exports导出声明,同时添加uses net.lightbluefoxlabs.dev.multirealm.core.multirealmcore.Realms.LoadedRealmPlugin;的ServiceLoader声明,保证插件模块对核心模块的依赖关系正确 - 问题校验:在异常抛出位置添加调试代码,分别打印
LoadedRealmPlugin.class.getClassLoader(),以及ServiceLoader加载的SurvivalRealm类的父类类加载器信息,如果两个类加载器实例不相同,即可确认是类重复加载导致的类型不兼容问题
内容的提问来源于stack exchange,提问作者user10619713
相关产品推荐
相关产品推荐

