首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >snakeyaml和spark导致无法构造对象。

snakeyaml和spark导致无法构造对象。
EN

Stack Overflow用户
提问于 2016-06-23 22:25:20
回答 1查看 2.3K关注 0票数 9

下面的代码在scala shell中执行得很好,给出了snakeyaml版本1.17

代码语言:javascript
复制
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor
import scala.collection.mutable.ListBuffer
import scala.beans.BeanProperty

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

  override def toString: String = {
    return s"acct ($accountName)"
  }
}

val text = """accountName: Ymail Account"""

val yaml = new Yaml(new Constructor(classOf[EmailAccount]))
val e = yaml.load(text).asInstanceOf[EmailAccount]
println(e)

但是,在spark中运行时(在本例中为2.0.0),产生的错误是:

代码语言:javascript
复制
org.yaml.snakeyaml.constructor.ConstructorException: Can't construct a java object for tag:yaml.org,2002:EmailAccount; exception=java.lang.NoSuchMethodException: EmailAccount.<init>()
 in 'string', line 1, column 1:
    accountName: Ymail Account
    ^

  at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:350)
  at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:182)
  at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:141)
  at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:127)
  at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:450)
  at org.yaml.snakeyaml.Yaml.load(Yaml.java:369)
  ... 48 elided
Caused by: org.yaml.snakeyaml.error.YAMLException: java.lang.NoSuchMethodException: EmailAccount.<init>()
  at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.createEmptyJavaBean(Constructor.java:220)
  at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.construct(Constructor.java:190)
  at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:346)
  ... 53 more
Caused by: java.lang.NoSuchMethodException: EmailAccount.<init>()
  at java.lang.Class.getConstructor0(Class.java:2810)
  at java.lang.Class.getDeclaredConstructor(Class.java:2053)
  at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.createEmptyJavaBean(Constructor.java:216)
  ... 55 more

我启动scala外壳时

代码语言:javascript
复制
scala -classpath "/home/placey/snakeyaml-1.17.jar"

我发射了火花壳

代码语言:javascript
复制
/home/placey/Downloads/spark-2.0.0-bin-hadoop2.7/bin/spark-shell --master local --jars /home/placey/snakeyaml-1.17.jar
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-11-19 02:31:24

解决方案

创建一个自给式应用,并使用spark-submit而不是spark-shell运行它。

我已经为您创建了一个最小的要点在这里项目。只需将两个文件(build.sbtMain.scala)放在某个目录中,然后运行:

代码语言:javascript
复制
sbt package

为了创建一个罐子。JAR将位于target/scala-2.11/sparksnakeyamltest_2.11-1.0.jar或类似的位置。如果您还没有使用它,您可以使用从这里把SBT叫来。最后,您可以运行该项目:

代码语言:javascript
复制
/home/placey/Downloads/spark-2.0.0-bin-hadoop2.7/bin/spark-submit --class "Main" --master local --jars /home/placey/snakeyaml-1.17.jar target/scala-2.11/sparksnakeyamltest_2.11-1.0.jar

产出应是:

代码语言:javascript
复制
[many lines of Spark's log)]
acct (Ymail Account)
[more lines of Spark's log)]

解释

星火的外壳(REPL)通过向构造函数中添加$iw参数来转换在其中定义的所有类。我有在这里解释过。SnakeYAML需要一个JavaBean类的零参数构造函数,但是没有一个,所以它失败了。

你可以自己试试:

代码语言:javascript
复制
scala> class Foo() {}
defined class Foo

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

scala> classOf[Foo].getConstructors()(0).getParameterCount
res1: Int = 1

如您所见,Spark通过添加一个类型为$iw的参数来转换构造函数。

替代解决方案

定义您自己的Constructor

如果您真的需要让它在shell中工作,您可以定义实现org.yaml.snakeyaml.constructor.BaseConstructor的自己的类,并确保将$iw传递给构造函数,但这是一项很大的工作(不久前出于安全原因,我实际上在Scala中编写了自己的Constructor,因此我对此有一些经验)。

您还可以定义一个自定义Constructor,硬编码以实例化一个类似于DiceConstructor 如SnakeYAML的文档所示的特定类(在您的情况下是EmailAccount)。这要容易得多,但需要为要支持的每个类编写代码。

示例:

代码语言:javascript
复制
case class EmailAccount(accountName: String)

class EmailAccountConstructor extends org.yaml.snakeyaml.constructor.Constructor {

  val emailAccountTag = new org.yaml.snakeyaml.nodes.Tag("!emailAccount")
  this.rootTag = emailAccountTag
  this.yamlConstructors.put(emailAccountTag, new ConstructEmailAccount)

  private class ConstructEmailAccount extends org.yaml.snakeyaml.constructor.AbstractConstruct {
    def construct(node: org.yaml.snakeyaml.nodes.Node): Object = {
      // TODO: This is fine for quick prototyping in a REPL, but in a real
      //       application you should probably add type checks.
      val mnode = node.asInstanceOf[org.yaml.snakeyaml.nodes.MappingNode]
      val mapping = constructMapping(mnode)
      val name = mapping.get("accountName").asInstanceOf[String]
      new EmailAccount(name)
    }
  }

}

您可以将其保存为一个文件,并使用:load filename.scala将其加载到REPL中。

该解决方案的优点是它可以直接创建不可变的case类实例。不幸的是,Scala似乎在导入方面存在问题,所以我使用了完全限定的名称。

不要使用JavaBeans

您还可以将YAML文档解析为简单的Java映射:

代码语言:javascript
复制
scala> val yaml2 = new Yaml()
yaml2: org.yaml.snakeyaml.Yaml = Yaml:1141996301

scala> val e2 = yaml2.load(text)
e2: Object = {accountName=Ymail Account}

scala> val map = e2.asInstanceOf[java.util.Map[String, Any]]
map: java.util.Map[String,Any] = {accountName=Ymail Account}

scala> map.get("accountName")
res4: Any = Ymail Account

这样,SnakeYAML就不需要使用反射。

但是,由于您使用的是Scala,我建议您尝试MoultingYAML,它是SnakeYAML的Scala包装器。它将YAML文档解析为简单的Java类型,然后将它们映射到Scala类型(甚至您自己的类型,如EmailAccount)。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38002883

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档