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

如何用java.util.ServiceLoader实例化Scala单例工厂对象

解决Scala单例工厂兼容java.util.ServiceLoader的问题

核心矛盾在于Scala单例对象(object)的编译类构造方法是私有,无法被ServiceLoader的Class.newInstance()实例化。下面提供两种可行的解决思路:

方案一:伴生类+单例对象的委托模式

这是最兼容Java ServiceLoader机制的方案,通过公共伴生类作为ServiceLoader的加载入口,内部逻辑委托给Scala单例对象实现。

步骤1:定义工厂接口

先定义一个公共接口,统一工厂的方法签名:

trait WidgetFactory {
  def fromJson(json: String): MyWidget
}

步骤2:实现单例工厂与伴生类

让Scala单例对象实现接口,同时定义一个公共伴生类,将所有方法委托给单例对象:

// 公共伴生类,作为ServiceLoader的加载目标
class WidgetJsonFactory extends WidgetFactory {
  // 所有逻辑委托给单例对象
  override def fromJson(json: String): MyWidget = WidgetJsonFactory.fromJson(json)
}

// Scala单例工厂,实现实际的JSON解析逻辑
object WidgetJsonFactory extends WidgetFactory {
  override def fromJson(json: String): MyWidget = {
    // 这里编写JSON字符串解析为MyWidget实例的逻辑
    // 示例:使用circe库解析
    import io.circe.generic.auto._
    import io.circe.parser.decode
    decode[MyWidget](json).getOrElse(throw new IllegalArgumentException("Invalid JSON"))
  }
}

步骤3:配置ServiceLoader

在项目资源目录下创建META-INF/services/文件夹,添加以接口全类名为名称的文件(比如com.example.WidgetFactory),文件内容写入伴生类的全类名:

com.example.WidgetJsonFactory

使用方式

通过ServiceLoader加载工厂实例,调用方法即可:

import java.util.ServiceLoader

val loader = ServiceLoader.load(classOf[WidgetFactory])
val factory = loader.iterator().next()
val widget = factory.fromJson("""{"name":"test"}""")

方案二:反射获取单例对象实例

如果不想定义伴生类,可以直接让单例对象实现工厂接口,然后通过反射获取单例对象的MODULE$实例,再注册到ServiceLoader的配置中。

步骤1:定义接口与单例工厂

trait WidgetFactory {
  def fromJson(json: String): MyWidget
}

object WidgetJsonFactory extends WidgetFactory {
  override def fromJson(json: String): MyWidget = {
    // 解析逻辑
    MyWidget(...)
  }
}

步骤2:配置ServiceLoader

在META-INF/services/com.example.WidgetFactory文件中写入单例对象的编译类名(注意末尾加$):

com.example.WidgetJsonFactory$

步骤3:自定义ServiceLoader加载逻辑

由于ServiceLoader默认的newInstance()会因构造私有失败,需要手动通过反射获取单例实例:

import java.util.ServiceLoader
import java.lang.reflect.Field

val loader = ServiceLoader.load(classOf[WidgetFactory])
val factory = loader.iterator().next() match {
  // 检测是否是Scala单例对象的实例,通过反射获取MODULE$
  case obj if obj.getClass.getName.endsWith("$") =>
    val moduleField = obj.getClass.getField("MODULE$")
    moduleField.get(null).asInstanceOf[WidgetFactory]
  case normalFactory => normalFactory
}

这种方式省去了伴生类,但需要额外的反射处理,不如方案一简洁可靠。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:02:07