• Jenkins加载Spring扩展库出错排查


    Photo by Roberto Nickson from Pexels

    1. 问题描述

    这段时间隔壁的PHP部门正在将原本使用Walle的交付体系逐渐迁移到Jenkins平台上,在Jenkins的使用上我也做过一段时间的探索,学习社区内优秀的思想。

    然而今天PHP部门的同事告诉我,原本由我参与开发的Jenkins扩展库无法运行在他们搭建的Jenkins上,且异常的内容看起来非常奇怪:

    java.lang.NoSuchMethodError: org.springframework.util.CollectionUtils.unmodifiableMultiValueMap(Lorg/springframework/util/MultiValueMap;)Lorg/springframework/util/MultiValueMap;
    	at org.springframework.web.util.HierarchicalUriComponents.<clinit>(HierarchicalUriComponents.java:60)
    	at org.springframework.web.util.UriComponentsBuilder.buildInternal(UriComponentsBuilder.java:469)
    	at org.springframework.web.util.UriComponentsBuilder.build(UriComponentsBuilder.java:459)
    	at org.springframework.web.util.UriComponentsBuilder.build(UriComponentsBuilder.java:446)
    	at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.build(DefaultUriBuilderFactory.java:403)
    	at org.springframework.web.util.DefaultUriBuilderFactory.expand(DefaultUriBuilderFactory.java:154)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    	at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:47)
    	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    	at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:20)
    

    从异常信息上来看是Spring工具类org.springframework.util.CollectionUtils下的unmodifiableMultiValueMap方法没有找到,进入Spring源码看这个方法是自Spring 3.1开始就提供的方法,算是比较早的了。

    2. 异常复现

    首先简单的模拟一下问题环境,Jenkins扩展库方面只引入一个Spring Core来复现工具类的问题,pom文件:

            <!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all -->
            <dependency>
                <groupId>org.codehaus.groovy</groupId>
                <artifactId>groovy-all</artifactId>
                <version>3.0.8</version>
                <type>pom</type>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.3.3</version>
            </dependency>
    
    

    然后加一个简单的类,提交到Git远程仓库中作为扩展库。

    /**
     @author fengxiao
     @date 2021-09-03
     */
    class CollectionUtilTest {
    
        void collectionIsEmpty(){
            println CollectionUtils.unmodifiableMultiValueMap(null)
        }
    
    }
    

    现在在Docker上运行较低版本的Jenkins容器:

    docker run --name jenkins1 --rm  -d -p 8081:8080 -p 50001:50000 jenkinsci/blueocean:1.23.2
    
    

    这里略过了Jenkins基本的初始化过程,例如:

    1. 设置Jenkins初始密码
    2. 设置系统插件更新地址为国内镜像源,目前比较好用的有Jenkins中文社区、清华镜像源、华为镜像源等。这里我选择了社区的镜像源地址:https://updates.jenkins-zh.cn/update-center.json
    3. 设置远程扩展库 (Configure System -> Global Pipeline Libraries)

    接下来编写一个Demo流水线:

    @Library('jenkins-support@master') _
    import com.landscape.jenkins.lib.CollectionUtilTest
    
    node{
        stage('test'){
            println "Test Spring Utils"
            new CollectionUtilTest().collectionIsEmpty()
        }
    }
    

    运行后复现出了相同的问题,图中的异常是hudson.remoting.ProxyException: groovy.lang.MissingMethodException.

    这是因为上面的Groovy代码中直接调用了不存在的方法,而如果是通过Spring的类库间接引用到就是java.lang.NoSuchMethodError

    3. 探究原因

    其实从上面的异常就已经大致能猜到是类加载错误导致的问题,但是日常使用Jenkins的时候很少会出现这样的问题,Google和StackOverflow上相似的情况非常少,

    在StackOverflow上有一个最相似的情况:https://stackoverflow.com/questions/39313324/nosuchmethoderror-when-developing-jenkins-plugin-with-spring-social-framework

    但并没有回答说明原因,所以我知道这个问题其实很简单,也简单的记录下,或许帮助遇到这类问题的同学。

    对于这类问题有一个非常简单粗暴的方案,即在Groovy中获取类的Jar包信息:

    /**
     @author fengxiao
     @date 2021-09-03
     */
    class CollectionUtilTest {
    
        String collectionIsEmpty() {
            return CollectionUtils.class.getResource("CollectionUtils.class").toString()
        }
    
    }
    
    

    从前面Spring源码中可以看到这个方法实际上是在Spring 3.1才加入到core包中,而这里实际上是从Spring2.5版本中获取的工具类,那当然找不到方法。

    熟悉Jenkins的都知道,Jenkins是基于Spring框架的项目,Jenkins的运行必定会加载特定版本的Spring类库,而再遇到相同的类时,即使我们指定了Spring版本也不会重新加载。

    所以解决这个问题的方案不是清空maven版本库,也不是添加hpi插件,而是提升Jenkins版本,在你的Jenkins_URL/about 页面可以看到Jenkins的依赖信息,本例运行的Jenkins镜像中的Jenkins是 2.249.1 ,依赖于Spring 2.5.6。

    而我日常工作所稳定运行的Jenkins版本是2.278,仅仅相隔一年不到,底层依赖的Spring已经提升到了spring-core:5.2.11。看来也是经过了一次大范围的重构。

    总结:流水线代码不是Java应用,没必要用花里胡哨的JVM技术来解决这种问题;Jenkins版本迭代也比较快,定期的维护升级可以保证稳定和高效运行

    这是一条签名的小尾巴: 任何变化都不是突然发生的,都是自己无意间一点一点选择的。
  • 相关阅读:
    Java GUI图形界面开发工具
    python操作MySQL数据库
    国外五大股票交易系统及其源码
    五个抄底摸高的交易系统和源代码
    海龟交易系统源码
    模式识别话题
    几种常见模式识别算法整理和总结
    比较好的开源人脸识别软件
    利用开源程序(ImageMagick+tesseract-ocr)实现图像验证码识别
    JSONObject与JSONArray的使用
  • 原文地址:https://www.cnblogs.com/novwind/p/15225543.html
Copyright © 2020-2023  润新知