如何在Scala中序列化函数?
我正在切断akka-persistence,并发现了对象序列化的典型问题。 我的对象(如下所示)具有基本类型和function。 我读了这个 , 这个和这个 ,但没有一个帮我制作下面的序列化。
测试工具
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 路由的回调,如下所示:
path("stats") { logRequest("/stats") { completeWith(instanceOf[List[Stats]]) { callback => requestHandler ! GetStatsRequest(callback) } } }
处理程序 actor会持久保存请求,直到获得响应。 构造最终输出可能需要不止一个响应。
我做了一些挖掘,看起来回调实现是一个CallbackRunnable 。
也许你没有完全理解链接的文章。 函数序列化的问题是闭包中捕获的任何东西也必须是可序列化的。 你需要的是孢子 。 一切都在那里解释,但这里有要点:
什么是关闭?
Scala中的Lambda函数可以引用外部作用域中的变量,而无需将它们明确地列为参数。 执行此操作的函数称为闭包,并且捕获它引用的外部变量。 例如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) }
现在(对于许多程序员来说意外),闭包捕获了非序列化封闭类的全部内容,包括myDBconn
,因为foo
引用了getter方法this.foo
。
解决方案是什么?
解决方案是不在封闭中捕获this
。 例如,为我们需要捕获的任何值创建局部值会使函数再次可序列化:
class C { val myDBconn = ... val foo = 42 { val localFoo = foo List(1,2,3).map(_ + localFoo) } }
当然,手动执行此操作非常繁琐,因此Spores 。