【问题标题】:How to serialize functions in Scala?如何在Scala中序列化函数?
【发布时间】:2018-05-28 03:33:45
【问题描述】:

我正在研究 akka-persistence,并遇到了对象序列化的典型问题。我的对象(如下所示)具有基本类型和功能。我读过thisthisthis,但没有一个帮助我使以下序列化。

测试工具

object SerializationUtil {
  def write(obj: Any): String = {
    val temp = Files.createTempFile(null, null).toFile
    val out = new ObjectOutputStream(new FileOutputStream(temp))
    out.writeObject(obj)
    out.close()

    temp.deleteOnExit()
    temp.getAbsolutePath
  }

  def read[T](file: String) = {
    val in = new ObjectInputStream(new FileInputStream(new File(file)))
    val obj = in.readObject().asInstanceOf[T]
    in.close()
    obj
  }
}

统计数据

case class Stats(
                  app: String,
                  unit: ChronoUnit,
                  private var _startupDurations: List[Long]
                ) {
  def startupDurations = _startupDurations.sorted

  def startupDurations_=(durations: List[Long]) = _startupDurations = durations

  @transient lazy val summary: LongSummaryStatistics = {
    _startupDurations.asJava.stream()
      .collect(summarizingLong(identity[Long]))
  }
}

Stats 序列化就好了。

"SerializationUtil" should "(de)serialize Stats" in {
  val file = SerializationUtil.write(newStats())
  val state = SerializationUtil.read[Stats](file)

  verifyStats(state)
}

但这不是:case class GetStatsForOneRequest(app: String, callback: Stats => Unit)

java.io.NotSerializableException: org.scalatest.Assertions$AssertionsHelper
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

也试过了:

trait SerializableRunnable[T] extends scala.Serializable with ((T) => Unit)

将回调实现为SerializableRunnable 的实例,但没有运气。

想法?

编辑

也许我应该澄清遇到此问题的实际用例以提供更多上下文。该函数是来自 Akka HTTP route 的回调,如下所示:

path("stats") {
  logRequest("/stats") {
    completeWith(instanceOf[List[Stats]]) { callback =>
      requestHandler ! GetStatsRequest(callback)
    }
  }
}

handler actor 会保持请求,直到它得到响应。构建最终输出可能需要多个响应。

我做了一些挖掘,似乎回调实现是CallbackRunnable

【问题讨论】:

  • 传递GetStatsForOneRequest时newStats()的实现是什么?它似乎适用于 GetStatsForOneRequest("app", _ => Unit)。
  • @igorpcholkin 只是一个带有虚拟值的 Stats。问题是回调函数
  • 我真正想看到的是你如何构造在 SerializationUtil.write 调用中使用的 GetStatsForOneRequest(包括回调)。

标签: java scala serialization akka-persistence


【解决方案1】:

也许您没有完全理解链接的文章。函数序列化的问题是闭包中捕获的任何内容也必须是可序列化的。你需要的是Spores。一切都在那里解释,但这里是要点:

什么是闭包?

Scala 中的 Lambda 函数可以引用外部范围内的变量,而无需将它们显式列为参数。执行此操作的函数称为 closure,它引用的外部变量是 captured。例如foo 在传递给map 下面的闭包中被捕获:

val foo = 42
List(1,2,3).map(_ + foo)

为什么是序列化的问题?

看看上面的例子,foo 是一个原始值,你不会认为这是个问题。但是当有一个封闭类时会发生什么?

class C {
  val myDBconn = ...
  val foo = 42
  List(1,2,3).map(_ + foo)
}

现在(许多程序员出乎意料)闭包捕获了不可序列化的封闭类的整个this,包括myDBconn,因为foo 指的是getter 方法this.foo

解决办法是什么?

解决方案是不要在闭包中捕获this。例如,为我们需要捕获的任何值创建一个本地 val 会使函数再次可序列化:

class C {
  val myDBconn = ...
  val foo = 42
  {
    val localFoo = foo
    List(1,2,3).map(_ + localFoo)
  }
}

当然,手动执行此操作很乏味,因此Spores

【讨论】:

  • 在我的问题中,我在哪里捕获this
  • 好的,我明白了。 callback 不受您的控制,因此您无法知道它捕获了什么。但是,如果您转到definition of completeWith,您会看到它捕获了一个不可可序列化的Promise。 Apropos,callback 这是一个延续函数,你必须在退出块之前调用它,而不是序列化。
  • 实际上,要完成块,必须调用回调。我当前的实现将它保存在地图中,这很有效。但我知道连载是另一回事
猜你喜欢
  • 2012-04-03
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
  • 2022-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
相关资源
最近更新 更多