如何在 Scala 中序列化函数?

Abh*_*kar 1 java serialization scala akka-persistence

我正在研究 akka 持久性并遇到了对象序列化的典型问题。我的对象(如下所示)具有基本类型和功能。我读了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
  }
}
Run Code Online (Sandbox Code Playgroud)

统计数据

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]))
  }
}
Run Code Online (Sandbox Code Playgroud)

Stats 序列化就好了。

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

  verifyStats(state)
}
Run Code Online (Sandbox Code Playgroud)

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

java.io.NotSerializableException: org.scalatest.Assertions$AssertionsHelper
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
Run Code Online (Sandbox Code Playgroud)

还试过:

trait SerializableRunnable[T] extends scala.Serializable with ((T) => Unit)
Run Code Online (Sandbox Code Playgroud)

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

想法?

编辑

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

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

处理器的演员坚持请求,直到得到响应。构建最终输出可能需要多个响应。

我做了一些挖掘,看来回调实现是CallbackRunnable

g.k*_*tev 5

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

什么是闭包?

Scala 中的 Lambda 函数可以引用外部作用域中的变量,而无需将它们明确列为参数。执行此操作的函数称为闭包,捕获它所引用的外部变量。例如foo在传递给map下面的闭包中捕获:

val foo = 42
List(1,2,3).map(_ + foo)
Run Code Online (Sandbox Code Playgroud)

为什么序列化会出现问题?

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

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

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

解决办法是什么?

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

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

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