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

如何使用Java ServiceLoader加载单例类实例

解决方案:ServiceLoader结合单例的实现方案

首先明确:Java ServiceLoader无法直接加载带有私有构造方法的类,因为它的底层实现依赖公共无参构造方法来实例化服务提供者(早期用Class.newInstance(),后来改用Constructor.newInstance(),都要求构造方法可访问)。所以要使用ServiceLoader,必须让实现类具备公共无参构造,下面提供两种安全的单例实现方式,同时兼容ServiceLoader:


方式一:带构造校验的线程安全单例

在公共无参构造中加入校验逻辑,防止重复实例化,确保单例的安全性:

public class MySingletonImpl implements MyInterface {
    // 懒汉式单例(线程安全)
    private static volatile MySingletonImpl instance;

    // 公共无参构造,供ServiceLoader调用
    public MySingletonImpl() {
        synchronized (MySingletonImpl.class) {
            if (instance != null) {
                throw new IllegalStateException("该类为单例,禁止重复实例化");
            }
            instance = this;
        }
    }

    // 保留原有的单例获取方法
    public static MySingletonImpl getInstance() {
        if (instance == null) {
            synchronized (MySingletonImpl.class) {
                if (instance == null) {
                    // 实际不会执行,因为ServiceLoader已完成初始化
                    new MySingletonImpl();
                }
            }
        }
        return instance;
    }

    // 实现接口方法
    @Override
    public void doBusiness() {
        // 业务逻辑实现
    }
}

核心逻辑:

  • ServiceLoader首次加载时调用公共构造,此时instance为null,完成单例初始化
  • 后续任何尝试实例化(包括反射调用构造)都会触发异常,阻止多实例创建
  • 双重校验锁保证并发场景下的线程安全

方式二:枚举单例+代理类

利用枚举天然的JVM级单例特性,通过代理类兼容ServiceLoader的要求:

1. 枚举实现核心单例逻辑

public enum MyCoreSingleton implements MyInterface {
    INSTANCE;

    @Override
    public void doBusiness() {
        // 核心业务逻辑
    }
}

2. 代理类供ServiceLoader加载

public class SingletonProxy implements MyInterface {
    // 内部持有枚举单例实例
    private final MyInterface delegate = MyCoreSingleton.INSTANCE;

    // 公共无参构造
    public SingletonProxy() {}

    // 代理所有接口方法
    @Override
    public void doBusiness() {
        delegate.doBusiness();
    }
}

核心逻辑:

  • 枚举单例由JVM保证绝对安全,无法被反射破坏
  • ServiceLoader加载的代理类实例,所有业务操作都会委托给同一个枚举单例
  • 终端用户只需将代理类配置到ServiceLoader服务文件中,隐藏核心单例细节

ServiceLoader的配置与使用

  1. 在项目的META-INF/services目录下,创建以接口全类名为文件名的文件(比如com.example.MyInterface)
  2. 文件中写入实现类的全类名(比如com.example.MySingletonImpl或com.example.SingletonProxy)
  3. 代码中加载服务:
ServiceLoader<MyInterface> serviceLoader = ServiceLoader.load(MyInterface.class);
// 遍历获取服务实例(单例只会初始化一次)
for (MyInterface service : serviceLoader) {
    service.doBusiness();
}

为什么比原反射方式性能好?

  • ServiceLoader会缓存已加载的服务实例,后续获取无需重复实例化
  • 避免了反射中Class.forName()、getDeclaredMethod()、invoke()等耗时操作
  • 懒加载机制只会在首次使用时加载服务,减少启动开销

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:15:54