You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Service Loader实例缓存异常:初始化后调用出现null config问题

问题:ServiceLoader缓存实例未生效,调用service()时config为null?

问题重现

定义服务接口:

public interface Svc {
    public void init(Config config);
    public void service();
}

实现类:

public class MySvc implements Svc {
    private Config config = null;
    public void init(Config config) {
        this.config = config;
    }
    public void service() {
       if (config == null) {
           System.err.println("null config");
       }
    }
}

启动阶段初始化服务:

public void bootstrap() {
    //initialise all providers
    serviceLoader.iterator().forEach(svcImpl -> svcImpl.init(config));
}

后续调用服务时,控制台打印null config:

serviceLoader.iterator().forEach(svcImpl -> svcImpl.service());

根据ServiceLoader的iterator方法文档:

Caching: The iterator returned by this method first yields all of the
elements of the provider cache, in the order that they were loaded. It
then lazily loads and instantiates any remaining service providers,
adding each one to the cache in turn. If this loader's provider caches
are cleared by invoking the reload method then existing iterators for
this service loader should be discarded. The hasNext and next methods
of the iterator throw ConcurrentModificationException if used after
the provider cache has been cleared.

结合提供的ServiceLoader迭代器源码:

@Override
public S next() {
    checkReloadCount();
    S next;
    if (index < instantiatedProviders.size()) {
        next = instantiatedProviders.get(index);
    } else {
        next = lookupIterator1.next().get();
        instantiatedProviders.add(next);
    }
    index++;
    return next;
}

原因分析与解决方案

出现该问题的核心是第二次调用iterator()时拿到未初始化的新实例,或初始化传入的config本身为null,以下是具体排查方向:

1. 初始化时传入的config本身为null

如果bootstrap()方法中传给init(config)的config变量本身是null,即使是同一个实例,调用service()时也会打印null config。

排查方式:在MySvc.init()中添加日志确认传入值:

public void init(Config config) {
    System.out.println("init config: " + config);
    this.config = config;
}

若输出为null,需修正bootstrap()中config的初始化逻辑,传入有效配置对象。

2. ServiceLoader缓存被意外清空

若在bootstrap()和service()调用之间执行了serviceLoader.reload(),会清空缓存的实例。第二次调用iterator()会重新加载新实例,这些实例未经过初始化,导致config为null。

排查方式:检查两段代码间的逻辑,确认是否存在reload()调用,如有则移除。

3. 第一次迭代未遍历到目标服务实例

若服务提供者配置文件(META-INF/services/[接口全类名])存在错误(类名拼写错误、路径不正确),或第一次迭代因异常提前终止,会导致MySvc实例未被加载到缓存。第二次迭代才加载该实例,且未经过初始化。

排查方式:

  • 验证配置文件:确保路径为src/main/resources/META-INF/services/xxx.Svc(替换为Svc接口全类名),文件内容为MySvc的全类名。
  • 在bootstrap()中添加日志,确认是否遍历到目标实例:
    serviceLoader.iterator().forEach(svcImpl -> {
        System.out.println("Loaded provider: " + svcImpl.getClass().getName());
        svcImpl.init(config);
    });
    
    若未输出MySvc类名,说明配置文件存在问题。

4. 自定义ServiceLoader的缓存逻辑缺陷

若使用的是自定义ServiceLoader而非JDK标准实现,可能存在缓存逻辑错误:

  • instantiatedProviders是迭代器局部变量,而非ServiceLoader成员变量,导致缓存无法跨迭代器共享;
  • lookupIterator1每次调用iterator()都重新扫描所有服务提供者,未正确判断实例是否已缓存,导致重复加载新实例。

排查方式:打印两次迭代中实例的哈希值,确认是否为同一对象:

// bootstrap阶段
serviceLoader.iterator().forEach(svcImpl -> {
    System.out.println("Bootstrap instance hash: " + svcImpl.hashCode());
    svcImpl.init(config);
});

// service阶段
serviceLoader.iterator().forEach(svcImpl -> {
    System.out.println("Service instance hash: " + svcImpl.hashCode());
    svcImpl.service();
});

若哈希值不同,说明加载了新实例,需检查自定义ServiceLoader的缓存逻辑,确保instantiatedProviders是ServiceLoader成员变量,且迭代时优先从缓存取实例。


内容的提问来源于stack exchange,提问作者Prashant Pandey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:35:19