詹金斯管道NotSerializableException:groovy.json.internal.LazyMap


80

已解决:感谢S.Richmond的以下答复。我需要取消设置所有groovy.json.internal.LazyMap类型的存储映射,这意味着使变量无效envServersobject在使用后。

附加:搜索此错误的人员可能有兴趣使用Jenkins管道步骤readJSON-在此处查找更多信息。


我正在尝试使用Jenkins Pipeline从用户那里获取输入,该输入作为json字符串传递给作业。管道然后使用隔离程序对此进行解析,然后我选择了重要信息。然后,它将使用该信息以不同的作业参数并行运行1个作业。

直到我"## Error when below here is added"在脚本下方添加代码,该脚本才能正常运行。甚至低于该点的代码也将独立运行。但是当结合在一起时,我得到以下错误。

我应该注意,已触发的作业已被调用并确实成功运行,但是发生以下错误并使主作业失败。因此,主作业不等待触发作业的返回。我可以尝试/赶上,build job:但是我希望主要作业等待触发的作业完成。

有人可以在这里协助吗?如果您需要更多信息,请告诉我。

干杯

def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}

node {
  stage 'Prepare';
  echo 'Loading choices as build properties';
  def object = slurpJSON();

  def serverChoices = [];
  def serverChoicesStr = '';

  for (env in object) {
     envName = env.name;
     envServers = env.servers;

     for (server in envServers) {
        if (server.Select) {
            serverChoicesStr += server.Server;
            serverChoicesStr += ',';
        }
     }
  }
  serverChoicesStr = serverChoicesStr[0..-2];

  println("Server choices: " + serverChoicesStr);

  ## Error when below here is added

  stage 'Jobs'
  build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]

}

错误:

java.io.NotSerializableException: groovy.json.internal.LazyMap
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
    at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
    at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
    at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
    at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
    in field delegate
    in field closures
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c

自己碰到这个。您有进一步的进步吗?
S.Richmond

Answers:


69

今天,我本人也遇到了这种情况,通过一些暴力手段,我弄清楚了如何解决它以及可能的原因。

最好从以下原因开始:

詹金斯(Jenkins)有一个范式,其中所有作业都可以通过服务器重启来中断,暂停和恢复。为此,管道及其数据必须完全可序列化-IE必须能够保存所有状态。类似地,它需要能够序列化构建中节点和子作业之间的全局变量状态,这就是我和我所想的,以及为什么只有在添加了额外的构建步骤后才会发生。

无论出于何种原因,默认情况下JSONObject都不可序列化。我不是Java开发人员,所以我很难过谈论这个话题。尽管我不知道它们对Groovy和Jenkins有多适用,但还有很多答案可以解决这个问题。有关更多信息,请参见这篇文章

解决方法:

如果您知道如何做,则可以使JSONObject以某种方式可序列化。否则,可以通过确保没有任何全局变量属于该类型来解决它。

尝试取消设置您的objectvar或将其包装在方法中,以使其范围不是节点全局的。


2
谢谢,这就是我需要解决的线索。虽然我已经尝试过您的建议,但它使我再次看上去,并且我没有考虑过将地图的一部分存储在其他变量中-这些都是导致错误的原因。所以我也需要取消设置它们。将修改我的问题,以包括对代码的正确更改。干杯
Sunvic

1
每天大约被浏览8次。你们介意提供有关如何实现此解决方案的更详细的示例吗?
乔丹·斯特凡内利

1
没有简单的解决方案,因为这取决于您所做的事情。这里提供的信息以及@Sunvic在其帖子顶部添加的解决方案足以使一个人找到自己的代码的解决方案。
S.Richmond

1
下面的解决方案使用JsonSlurperClassic修复了我遇到的完全相同的问题,应该是这里的认可选择。这个答案是有好处的,但这不是解决此特定问题的正确方法。
石英

@JordanStefanelli我发布了解决方案的代码。请参阅下面的答案
Nils El-Himoud,

127

使用JsonSlurperClassic代替。

从Groovy 2.3(注意:Jenkins 2.7.1使用Groovy 2.4.7)开始,JsonSlurper返回LazyMap而不是HashMap。这使得JsonSlurper 线程安全且不可序列化的新实现成为可能。这使得它无法在管道DSL脚本中的@NonDSL函数之外使用。

但是,您可以回退到groovy.json.JsonSlurperClassic支持旧行为的地方,并且可以在管道脚本中安全地使用它。

import groovy.json.JsonSlurperClassic 


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

node('master') {
    def config =  jsonParse(readFile("config.json"))

    def db = config["database"]["address"]
    ...
}    

ps。您仍然需要批准JsonSlurperClassic才能调用它。


2
你能告诉我如何批准JsonSlurperClassic吗?
mybecks '16

7
Jenkins管理员将需要导航至Manage Jenkins»进程内脚本批准。
luka5z

不幸的是,我只得到hudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic
dvtoever

13
JsonSluperClassic ..该名称充分说明了软件开发的当前状态
Marcos Brigante

1
非常感谢您的详细解释。您节省了我很多时间。此解决方案在我的詹金斯管道中像魅力一样。
Sathish Prakasam

16

编辑:正如@Sunvic在评论中指出的那样,以下解决方案无法按原样用于JSON数组。

我使用了懒惰的结果JsonSlurper,然后创建了一个新HashMap的结果来处理这个问题。HashMapSerializable

我认为这需要将new HashMap(Map)和都列入白名单JsonSlurper

@NonCPS
def parseJsonText(String jsonText) {
  final slurper = new JsonSlurper()
  return new HashMap<>(slurper.parseText(jsonText))
}

总体来说,我建议只使用管道实用程序步骤的插件,因为它有一个readJSON步骤,可以支持在工作区或文本文件两种。


1
对我没用-不断出错Could not find matching constructor for: java.util.HashMap(java.util.ArrayList)。文档建议它应该吐出列表或地图-如何配置以返回地图?
Sunvic

@Sunvic很好,我们一直在解析的数据始终是对象,而不是JSON数组。您是否要解析JSON数组?
mkobit

是的,这是一个JSON数组。
Sunvic

在詹金斯
身上

@yiwen我提到需要将管理员列入白名单,但是也许可以弄清楚答案是什么意思?
mkobit


5

来自@mkobit的答案的稍微更笼统的形式将允许对数组以及映射进行解码:

import groovy.json.JsonSlurper

@NonCPS
def parseJsonText(String json) {
  def object = new JsonSlurper().parseText(json)
  if(object instanceof groovy.json.internal.LazyMap) {
      return new HashMap<>(object)
  }
  return object
}

注意:请注意,这只会将顶级LazyMap对象转换为HashMap。任何嵌套的LazyMap对象将仍然存在,并继续导致Jenkins出现问题。


5

这是所要求的详细答案。

这种情况对我有用:

String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2

// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null

我从解析的响应中读取值,当不再需要该对象时,我将其取消设置。



2

根据Jenkins博客上发布的最佳实践(“管道可扩展性最佳实践”),强烈建议使用命令行工具或脚本来进行此类工作:

不可思议:尤其要避免使用Groovy的XmlSlurper和JsonSlurper进行管道XML或JSON解析!强烈希望使用命令行工具或脚本。

一世。Groovy实现很复杂,因此在管道使用中更加脆弱。

ii。XmlSlurper和JsonSlurper会在管道中带来很高的内存和CPU成本

iii。xmllint和xmlstartlet是通过xpath提供XML提取的命令行工具

iv。jq为JSON提供相同的功能

v。这些提取工具可以耦合到curl或wget以便从HTTP API提取信息

因此,它解释了为什么默认情况下,Jenkins安全脚本插件的沙箱会阻止此页面上提出的大多数解决方案。

Groovy的语言哲学比Python或Java更接近Bash。而且,这意味着在本机Groovy中进行复杂而繁重的工作是不自然的。

鉴于此,我个人决定使用以下内容:

sh('jq <filters_and_options> file.json')

有关更多帮助,请参见jq手册使用jq stackoverflow发布选择对象

这有点反直观,因为Groovy提供了许多不在默认白名单中的通用方法。

如果您决定在大多数工作中仍然使用Groovy语言,并且启用并清理了沙箱(这并不容易,因为不自然),建议您检查安全脚本插件版本的白名单,以了解可能的情况:脚本安全插件白名单


2

您可以使用以下函数将LazyMap转换为常规LinkedHashMap(它将保留原始数据的顺序):

LinkedHashMap nonLazyMap (Map lazyMap) {
    LinkedHashMap res = new LinkedHashMap()
    lazyMap.each { key, value ->
        if (value instanceof Map) {
            res.put (key, nonLazyMap(value))
        } else if (value instanceof List) {
            res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
        } else {
            res.put (key, value)
        }
    }
    return res
}

... 

LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);

或更好地使用readJSON步骤,如先前注释中所述:

Map serializableMap = readJSON text: jsonText

1

这篇文章中的其他想法很有帮助,但并不是我所需要的全部-因此,我提取了适合我需要的部分并添加了一些自己的magix ...

def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
    return new JsonSlurperClassic().parseText(
        new JsonBuilder(
            new JsonSlurper()
                .setType(JsonParserType.LAX)
                .parseText(jsonText)
        )
        .toString()
    )
}

是的,正如我在自己的代码的git commit中指出的那样:“通俗无能,但系数很小:JSON slurp解决方案”(为此,我可以接受)。我需要解决的方面:

  1. 完全摆脱java.io.NotSerializableException问题,即使JSON文本定义了嵌套容器
  2. 适用于地图和阵列容器
  3. 支持LAX解析(对我而言,最重要的部分)
  4. 易于实现(即使使用笨拙的嵌套构造函数也可以避免@NonCPS

1

Noob犯了我的错误。从旧的管道插件jenkins 1.6移走了某人的代码?到运行最新的2.x jenkins的服务器。

由于以下原因而失败:“ java.io.NotSerializableException:groovy.lang.IntRange”对于上述错误,我多次阅读和阅读这篇文章。已实现:for(1..numSlaves中的num){IntRange-不可序列化的对象类型。

以简单形式重写:for(num = 1; num <= numSlaves; num ++)

世界一切都很好。

我不经常使用Java或groovy。

谢谢你们。


0

我在Jenkins管道的文档中找到了更简单的方法

工作实例

import groovy.json.JsonSlurperClassic 


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

@NonCPS
def jobs(list) {
    list
        .grep { it.value == true  }
        .collect { [ name : it.key.toString(),
                      branch : it.value.toString() ] }

}

node {
    def params = jsonParse(env.choice_app)
    def forBuild = jobs(params)
}

由于工作流的限制(即JENKINS-26481),实际上不可能使用Groovy闭包或依赖于闭包的语法,因此您不能>在列表上使用.collectEntries并将步骤作为值生成的Groovy标准用于生成的条目。对于For循环,您也不能使用标准> Java语法-即“ for(String s:strings)”-而是必须使用基于计数器的老式循环。


1
建议使用Jenkins管道步骤readJSON代替-在此处查找更多信息。
Sunvic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.