Java ServiceLoader包装类失效:调用类识别错误问题
解决ServiceLoader调用者模块未声明uses的问题
方案1:使用预声明uses的代理模块
构建一个专门的代理模块,在其module-info.java中声明所有你需要加载的服务类型的uses语句,把这个模块加入到自定义ModuleLayer中,然后通过该模块内的工具类发起ServiceLoader调用——这样调用者模块就是代理模块,它已经满足uses声明要求。
示例:
- 代理模块的
module-info.java:
module plugin.proxy { uses com.yourproject.services.ServiceA; uses com.yourproject.services.ServiceB; // 按需添加所有需要加载的服务类型 }
- 代理模块内的工具类:
package com.yourproject.proxy; import java.util.ServiceLoader; public class PluginServiceLoader { public static <S> ServiceLoader<S> load(ModuleLayer layer, Class<S> serviceType) { // 这里的调用者是PluginServiceLoader所在的plugin.proxy模块,已声明uses return ServiceLoader.load(layer, serviceType); } }
- 在你的
PluginLoader中调用:
public <S> ServiceLoader<S> loadServices(ModuleLayer layer, Class<S> serviceType) { return PluginServiceLoader.load(layer, serviceType); }
优点:代码简洁,复用性强,无需反射;缺点:需要提前知道所有服务类型,无法适配完全动态的服务场景。
方案2:手动遍历模块收集服务提供者
绕开ServiceLoader的调用者检查逻辑,直接遍历目标ModuleLayer中的所有模块,手动提取服务提供者实例。这种方式不依赖调用者模块的uses声明,完全自主控制服务加载流程。
示例代码:
public <S> List<S> loadServices(ModuleLayer layer, Class<S> serviceType) { List<S> serviceInstances = new ArrayList<>(); for (Module module : layer.modules()) { // 遍历模块中所有该服务的提供者 module.findServiceProviders(serviceType) .forEach(provider -> { try { serviceInstances.add(provider.get()); } catch (Exception e) { // 根据业务需求处理实例化异常,比如打印日志或跳过 e.printStackTrace(); } }); } return serviceInstances; }
优点:完全适配动态服务场景,无需额外模块;缺点:需要自行处理服务加载的异常、缓存等逻辑,不如ServiceLoader封装完善。
方案3:利用插件模块自身作为调用者
如果你的插件模块本身会声明uses(比如插件需要使用其他服务),可以从加载到ModuleLayer的插件模块中获取一个类,通过反射以该类作为调用者发起ServiceLoader调用。
示例代码:
public <S> ServiceLoader<S> loadServices(ModuleLayer layer, Class<S> serviceType) throws Exception { // 找到第一个提供该服务的插件模块 Optional<Module> targetModule = layer.modules().stream() .filter(module -> module.findServiceProviders(serviceType).findAny().isPresent()) .findFirst(); if (targetModule.isEmpty()) { return ServiceLoader.load(layer, serviceType); } // 获取模块中的任意类作为调用者 Class<?> callerClass = targetModule.get().getClasses()[0]; Method loadMethod = ServiceLoader.class.getMethod("load", ModuleLayer.class, Class.class); // 以callerClass的类加载器上下文执行调用,确保调用者模块是插件模块 return (ServiceLoader<S>) loadMethod.invoke(null, layer, serviceType); }
优点:无需额外模块;缺点:依赖插件模块自身声明uses,如果插件仅提供服务不使用服务则会失效,且反射调用略繁琐。
内容的提问来源于stack exchange,提问作者Wexalian
相关产品推荐
相关产品推荐

