简体   繁体   English

如何在Scala中序列化函数?

[英]How to serialize functions in Scala?

I'm cutting my teeth on akka-persistence and came upon the quintessential problem of object serialization. 我正在削弱akka-persistence,并发现了对象序列化的典型问题。 My objects (shown below) have basic types, and functions. 我的对象(如下所示)具有基本类型和功能。 I read this , this and this , but none has helped me in making the following serializable. 我读了这个这个这个 ,但没有一个帮我制作下面的序列化。

Test Util 测试工具

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
  }
}

Stats 统计

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 serializes just fine. Stats序列化很好。

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

  verifyStats(state)
}

But this doesn't: case class GetStatsForOneRequest(app: String, callback: Stats => Unit) 但这不是: case class GetStatsForOneRequest(app: String, callback: Stats => Unit)

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

Also tried: 还尝试过:

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

implementing the callback as an instance of SerializableRunnable , but no luck. 将回调实现为SerializableRunnable一个实例,但没有运气。

Ideas? 想法?

Edit : 编辑

Perhaps I should clarify the actual use case that is running into this issue to provide more context. 也许我应该澄清在此问题中遇到的实际用例以提供更多上下文。 The function is a callback from Akka HTTP route like the following: 该函数是来自Akka HTTP 路由的回调,如下所示:

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

The handler actor persists the request until it gets a response. 处理程序 actor会持久保存请求,直到获得响应。 It may take more than one response to construct the final output. 构造最终输出可能需要不止一个响应。

I did some digging and it appears that the callback implementation is a CallbackRunnable . 我做了一些挖掘,看起来回调实现是一个CallbackRunnable

Maybe you didn't fully understand the linked articles. 也许你没有完全理解链接的文章。 The problem with function serialization is that anything captured in the closure must also be serializable. 函数序列化的问题是闭包中捕获的任何东西也必须是可序列化的。 What you need is Spores . 你需要的是孢子 Everything is explained there, but here is gist: 一切都在那里解释,但这里有要点:

What is a closure? 什么是关闭?

Lambda functions in Scala can refer to variables in the outer scope without explicitly listing them as parameters. Scala中的Lambda函数可以引用外部作用域中的变量,而无需将它们明确地列为参数。 A function that does this is called a closure and the outer variables it refers to are captured . 执行此操作的函数称为闭包,并且捕获它引用的外部变量。 For example foo is captured in closure passed to map below: 例如foo是在传递给下面的map闭包中捕获的:

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

Why is it a problem for serialization? 为什么序列化存在问题?

Looking at the example above where foo is a primitive value, you woudn't think that's a problem. 看看上面foo是原始值的例子,你不认为这是一个问题。 But what happens when there is an enclosing class? 但是当有一个封闭的课程时会发生什么?

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

Now (unexpectedly for many programmers) the closure captures the entire this of the non-serializable enclosing class, including myDBconn , because foo refers to the getter method this.foo . 现在(意外许多程序员)关闭捕获整个this非序列化封装类,包括myDBconn ,因为foo指getter方法this.foo

What's the solution? 解决方案是什么?

The solution is to not capture this in the closure. 解决方案是不要在封闭中捕获this For example creating a local val for any value we need to capture makes the function serializable again: 例如创建本地val因为我们需要捕获任何值使得再次函数序列化:

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

Of course, doing this manually is tedious, hence Spores . 当然,手动执行此操作非常繁琐,因此Spores

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM