增量部署包出现java.lang.NoSuchMethodError异常解决过程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huangying2124/article/details/72848770

一、环境概述
1、此次项目使用的环境: Jboss 4.2.3.GA,JDK1.7.0_79,Eclipse Mars.2 版本,Maven3.3.9。
2、项目发布采用增量部署的方式,即版本发布时,只部署修改过的类或jsp文件,这样可以提高版本的稳定性,降低未知的发布风险。

二、打包过程
采用增量打包的方式,只将有修改的类编译的class文件放在生成的war包里,再通过jar命令将增量的war文件压缩到运行环境的war包内。

三、出现的状况
1、研发环境(Eclipse环境下)正常使用。
2、测试环境(增量包部署后)出现了如下异常:

Exception in thread "main" java.lang.NoSuchMethodError: com.hy.method.MethodModify.update(Lcom/hy/truelicense/LicenseBean;)V
        at com.hy.method.MethodClient.main(MethodClient.java:9)

3、检查修改类反编译的结果,发现方法已经是最新版本的类编译生成的class文件。

四、问题模拟重现及追溯
为了重现该问题,单独写了两个非常小的demo类:MethodModify和MethodClient ,如下:
方法声明示例类MethodModify 代码(修改前):

package com.hy.method;
public class MethodModify {
    public void update(String str) {
        System.out.println(str);
    }
}

方法声明示例类MethodModify 代码(修改后):

package com.hy.method;
public class MethodModify {
    public int update(String str) {
        System.out.println(str);
        return str.hashCode();
    }
}

方法调用示例类MethodClient代码:

package com.hy.method;
public class MethodClient {
     public static void main(String[] args) {
           MethodModify mm = new MethodModify();
            mm.update( "hello");
     }
}

MethodModify这个类修改方法签名,update方法原有的返回值是void,现改为int,查看该类的class文件,使用命令javap -verbose MethodModify.class或javap -c MethodModify.class
针对比较大的class文件,可以把查看的字节码内容输出到txt文件当中,方便阅读,搜索,命令如下:javap -verbose MethodModify.class > MethodModify.txt

示例类MethodModify取修改后的字节码片断:

public int update(com.hy.truelicense.LicenseBean);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_1       
         4: invokevirtual #22                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
         7: iconst_1     
         8: ireturn       
      LineNumberTable:
        line 8: 0
        line 9: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  this   Lcom/hy/method/MethodModify;
               0       9     1  bean   Lcom/hy/truelicense/LicenseBean;

可以看到方法声明返回值类型为int,说明该类的最新修改已经编译成class文件了。

方法调用示例类MethodClient取字节码片断:

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #16                 // class com/hy/method/MethodModify
         3: dup           
         4: invokespecial #18                 // Method com/hy/method/MethodModify."<init>":()V
         7: astore_1     
         8: aload_1       
         9: new           #19                 // class com/hy/truelicense/LicenseBean
        12: dup           
        13: invokespecial #21                 // Method com/hy/truelicense/LicenseBean."<init>":()V
        16: invokevirtual #22                 // Method com/hy/method/MethodModify.update:(Lcom/hy/truelicense/LicenseBean;)V
        19: return       
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      20     0  args   [Ljava/lang/String;
               8      12     1    mm   Lcom/hy/method/MethodModify;

阅读字节码可以发现,调用类字节码调用update方法的关键行“Method com/hy/method/MethodModify.update:(Lcom/hy/truelicense/LicenseBean;)V ,标识是个“V”,表示void,即要找的是返回值为void的update方法,而新改的方法声明类,update方法返回值类型是int,方法签名不一致,所以会出现
java.lang.NoSuchMethodError: com.hy.method.MethodModify.update(Lcom/hy/truelicense/LicenseBean;)V。

以上可以说明3个问题:
1、修改的java类更新成了最新的class,但调用该方法的class类未更新,导致方法不一致。
2、打增量包的方式并不能保证所有关联的class都是最新的。
3、Eclipse一般会刷新所有的class类,比如使用maven命令clean package等,或是Eclipse自带的project—>clean操作,都会清空之前生成的class,然后重新编译生成一套新的class,所以研发环境是正常的,但测试环境会抛出异常,根本原因是部分依赖的class未更新,还是使用方法签名修改之前编译好的class,与最新的方法不一致,因而会出现java.lang.NoSuchMethodError异常。

五、解决办法
在Eclise上搜索所有使用到了MethodModify类的update方法的类,将这些类最新编译的class作为增量包的一部分部署到测试环境中。

六、经验总结:
探讨一下什么情况会出现class不同步的现象?分析一下
1、修改的类有新增的方法,若调用类有使用到新的方法,会修改调用类的代码,此调用类编译的class也会作为增量包的一部分,不会出现class不同步的现象。
2、修改的类有删除的方法或成员变量,若调用类也删除了该方法的调用,同理会出现在增量包中,若未涉及到此方法,不会受影响,此场景也不会出现class不同步的现象。
3、修改了方法的签名,比如返回值由void变成int,调用类有使用这个方法的,编译环境不会摄氏,极有可能会导致class不同步。

根据以上探讨的结果可知,以后打增量包要注意,若出现修改方法签名又不会直接导致编译失败的,要多注意一下class的同步问题。
另外,javap工具对此次诊断问题很有帮助。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页