如何用自定义类加载器从模块层加载服务提供者并解决加载异常
错误信息
Exception in thread "main" java.util.ServiceConfigurationError: client.MyInterface: Provider implementation.MyInterfaceImpl not found
相关代码
ClassLoader classLoader = new CustomClassLoader(); String module = "implementation"; Path dir = Path.of("/path/to/lib.jar"); ModuleFinder finder = ModuleFinder.of(dir); ModuleLayer parent = ModuleLayer.boot(); Configuration conf = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of(module)); ModuleLayer layer = parent.defineModules(conf, (String name) -> classLoader); //layer.findLoader(module).loadClass("implementation.MyInterfaceImpl"); ServiceLoader<MyInterface> loader = ServiceLoader.load(layer, MyInterface.class); Iterator<MyInterface> iterator = loader.iterator(); for(MyInterface impl: loader){ impl.doSomething(); }
问题背景
目前通过手动添加 layer.findLoader(module).loadClass("implementation.MyInterfaceImpl"); 可以绕过错误,但不想采用这种方式——因为不想在编译时硬编码实现类的全限定名。需要明确:
- 该错误的根本原因是什么?
- 为什么ServiceLoader没有自动调用模块层对应的类加载器加载提供者类?
问题原因分析
这个问题的核心是自定义类加载器逻辑与ServiceLoader的预期不匹配,具体分为两点:
自定义类加载器的实现缺陷
你指定的CustomClassLoader如果没有正确实现类加载逻辑,比如未正确从目标JAR中查找类、委托顺序违背双亲委派模型,会导致ServiceLoader尝试加载提供者类时,类加载器无法定位到目标类。ServiceLoader依赖模块层关联的类加载器完成类加载,但如果自定义加载器的路径搜索、委托逻辑出错,自然找不到类。模块配置与ServiceLoader懒加载的冲突
ServiceLoader是懒加载机制,只有遍历迭代器时才会触发提供者类加载。而手动调用loadClass是直接通过模块层加载器明确触发类加载,绕过了ServiceLoader内部可能存在的类路径或模块可见性问题。另外,如果模块描述符(module-info.java)配置错误——比如服务接口模块未声明uses、提供者模块未声明provides,ServiceLoader根本无法通过模块系统发现提供者,进而触发找不到类的错误。
解决方法
修正自定义类加载器实现
确保CustomClassLoader的findClass方法能正确从/path/to/lib.jar中读取并加载类,同时遵循双亲委派模型(特殊需求除外):先委托父类加载器尝试加载,再自行加载模块内的类。可以在加载逻辑中添加日志,验证请求加载implementation.MyInterfaceImpl时,类加载器是否能访问到目标JAR。检查模块描述符配置
- 服务接口所在模块(client模块)的
module-info.java需声明服务:module client { exports client.MyInterface; uses client.MyInterface; } - 服务提供者所在模块(implementation模块)的
module-info.java需声明提供服务:module implementation { requires client; provides client.MyInterface with implementation.MyInterfaceImpl; }
模块配置错误会导致ServiceLoader无法通过模块系统发现提供者,直接引发类找不到的错误。
- 服务接口所在模块(client模块)的
确认模块层与类加载器的关联
在调用parent.defineModules(conf, (String name) -> classLoader)时,要确保返回的CustomClassLoader确实关联了目标模块的JAR路径,避免出现类加载器与模块资源脱节的情况。
内容的提问来源于stack exchange,提问作者JasonBourne

