为什么 scala.beans.beanproperty 在 spark 中的工作方式不同

Why does scala.beans.beanproperty work differently in spark

在 Scala REPL 中使用以下代码

import scala.beans.BeanProperty

class EmailAccount {
  @scala.beans.BeanProperty var accountName: String = null

  override def toString: String = {
    return s"acct ($accountName)"
  }
}
classOf[EmailAccount].getDeclaredConstructor()

结果

res0: java.lang.reflect.Constructor[EmailAccount] = public EmailAccount()

但是在 spark 的 REPL 中我得到

java.lang.NoSuchMethodException: EmailAccount.<init>()
  at java.lang.Class.getConstructor0(Class.java:2810)
  at java.lang.Class.getDeclaredConstructor(Class.java:2053)
  ... 48 elided

造成这种差异的原因是什么?我怎样才能得到火花来匹配火花的行为 shell.

我是这样启动 REPL 的:

/home/placey/Downloads/spark-2.0.0-bin-hadoop2.7/bin/spark-shell --master local --jars /home/placey/snakeyaml-1.17.jar

scala -classpath "/home/placey/snakeyaml-1.17.jar

Scala 版本是 火花:

Using Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55)

scala:

Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55).

实际上,这并非特定于 scala.beans.BeanProperty 甚至 Spark。您可以通过 运行 使用 -Yrepl-class-based 参数在标准 Scala REPL 中获得相同的行为:

scala -Yrepl-class-based

现在,让我们尝试定义一个简单的空 class:

scala> class Foo()
defined class Foo

scala> classOf[Foo].getConstructors
res0: Array[java.lang.reflect.Constructor[_]] = Array(public Foo($iw))

scala> classOf[Foo].getFields
res1: Array[java.lang.reflect.Field] = Array(public final $iw Foo.$outer)

如您所见,REPL 通过向构造函数添加额外的字段和参数来即时修改您的 class。为什么?

每当你在 Scala REPL 中创建一个 valvar 时,它都会被包裹在一个特殊的对象中,因为在 Scala 中没有 "global variables" 这样的东西。参见 this answer

通常情况下,这是一个对象,所以它是全局可用的。但是,对于 -Yrepl-class-based,REPL 使用 class 个实例而不是单个全局对象。此功能由 Spark 开发人员引入,因为 Spark 需要 classes 可序列化,以便将它们发送给远程工作者(请参阅 this pull request)。

因此,您在 REPL 中定义的任何 class 都需要获取 $iw 实例。否则,您将无法访问您在 REPL 中定义的全局 vals 和 vars。此外,生成的 class 会自动扩展 Serializable.

恐怕你无法阻止这种情况spark-shell 默认启用 -Yrepl-class-based。即使有禁用此行为的选项,您也会 运行 遇到许多其他问题,因为您的 classes 将不再可序列化,但 Spark 需要序列化它们。