如何用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
相关产品推荐
相关产品推荐

