You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中trait内定义的case class无法序列化问题求解

问题根因

定义在trait内部的case class属于路径依赖类型,通过DataStore.User构造的实例会隐式持有对外部封闭实例DataStore$(即DataStore单例对象)的$outer引用。Java原生序列化机制会递归序列化对象的所有非transient字段,包括这个隐式持有的外部引用,但DataStore单例未实现Serializable接口,因此抛出NotSerializableException,异常栈中提示的无法序列化的类tests.DataStore$就是这个单例对象的类型。

解决方案(无需修改slick自动生成的trait代码)

方案1:给继承Tables的单例混入Serializable(最小改动,优先推荐)

不需要改动自动生成的trait Tables代码,只需要在自行编写的、继承Tables的单例对象上混入Serializable特质即可:

package test

import java.io.{ByteArrayOutputStream, ObjectOutputStream}


trait Tables {
  case class User(id: Int, name: String)
}

// 仅修改这一行,给DataStore混入Serializable
object DataStore extends Tables with Serializable {
  // 原有业务逻辑不需要任何改动
}

object ScalaCacheTest {

  def serialize(value: Serializable): Array[Byte] = {
    val buf = new ByteArrayOutputStream()
    val os = new ObjectOutputStream(buf)
    os.writeObject(value)
    os.close()
    buf.toByteArray
  }

  def main(args: Array[String]) = {
     val user = DataStore.User(9527, "star")
    serialize(user)
    println("tests pass")
  }
}

JDK原生对单例对象的序列化有特殊处理,反序列化时会保证返回唯一的单例实例,不会产生多余的对象副本,不会影响原有业务逻辑。

方案2:自定义序列化逻辑跳过不可序列化的$outer引用

如果不方便修改DataStore的继承关系,可以自定义ObjectOutputStream,在序列化时处理路径依赖类型的隐式外部引用,避免递归序列化不可序列化的对象:

import java.io.{ByteArrayOutputStream, ObjectOutputStream}
import scala.util.Try

def serialize(value: AnyRef): Array[Byte] = {
  val buf = new ByteArrayOutputStream()
  val os = new ObjectOutputStream(buf) {
    override def writeObject(obj: Any): Unit = {
      val targetObj = obj.asInstanceOf[AnyRef]
      // 查找路径依赖类型隐式持有的$outer字段
      Try(targetObj.getClass.getDeclaredField("$outer")).toOption.foreach { field =>
        field.setAccessible(true)
        val outerInstance = field.get(targetObj)
        // 如果外部引用对象不可序列化,将字段置为null避免序列化报错
        if (outerInstance != null && !classOf[Serializable].isAssignableFrom(outerInstance.getClass)) {
          field.set(targetObj, null)
        }
      }
      super.writeObject(obj)
    }
  }
  os.writeObject(value)
  os.close()
  buf.toByteArray
}

该方案需要注意反序列化时的兼容逻辑,避免$outer字段为null触发路径依赖类型校验错误,仅适合无法修改DataStore定义的场景。

方案3:替换为专业序列化框架(长期缓存场景推荐)

Java原生序列化本身性能差、序列化后体积大,并不适合做缓存序列化。可以替换为Kryo、uPickle、ScalaPB等专门的序列化框架,这类框架不会递归序列化路径依赖类型隐式持有的$outer引用,也不需要类实现Serializable接口,性能远高于Java原生序列化,且能天然规避这类问题。以Kryo为例,只需要提前注册对应的case class即可正常完成序列化/反序列化,不需要修改任何已有业务代码。


内容的提问来源于stack exchange,提问作者crowhite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:54:31