从NoSuchMethodError看jvm编译和class加载方式

来源:互联网 发布:go开头的软件 编辑:程序博客网 时间:2024/05/16 14:20
今天在写自己的项目的时候出现了一个Exception in thread "main" java.lang.NoSuchMethodError的异常,但是我的代码在编译过程中是没有任何问题的。
先来讲一下我的项目关键的结构


我的项目引用了两个jar包,暂定为jarA(version:1.0)和jarB(version:2.0),但是jarA中又引用了jarB(version:1.0),这个时候我调用了jarA中的一个方法,假定为invokeMethodA(),该方法会调用jarB中的一个方法,这个方法jarA中的jarB(version:1.0)是这样的格式invokeMethodB(String s,Throwable t);但是这个方法在jarB是这样的invokeMethodB(String s,Object... o);相当于jarB(version:2.0)进行了一个升级。当我调用这个方法的时候jarA的invokeMethodA()方法,则会报出这样的问题Exception in thread "main" java.lang.NoSuchMethodError: com.cn.b.BClass.invokeMethodB(Ljava/lang/String;Ljava/lang/Throwable;)V。这是因为在jarA中的jarB(version:1.0)中,invokeMethodB在jarB(version:1.0)中编译的时候是编译成invokeMethodB(Ljava/lang/String;Ljava/lang/Throwable;)V的形式,只有完全匹配上才能够调得通。但是在这边的项目中有完全同包名同类名同方法但是参数格式不一样的方法,被编译成(Ljava/lang/String;[Ljava/lang/Object;)V的格式,而BClass是优先加载jarB(version:2.0)中的BClass。


这里得出两个结论
1、对于同包名同类名的类,类加载器会加载引用链最短的jar包内的类
2、编译器在方法调用前就完全确定一个方法的一一匹配



下面是我简单的自己写了方法进行测试

先看看项目结构



先看测试方法的Test类

public class Test {    public static void main(String[] args) {        new PrintUtil().print("dd");    }}


再看看jarB(version:2.0)模拟的T1实现

public class T1 {    public void doit(String dd, Object... o) {        System.out.println(dd + " --- object...");    }}


下面看jar-test模块所引用的jar包 jar1(对应上面说的jatA)

先来看PrintUtil类(类似于项目中要调用第三方jar包的方法)

public class PrintUtil {    public void print(String str) {        T1 t1 = new T1();        t1.doit(str,new Exception());    }}<

最后看jarA中对应jarB(version:1.0)对应的类方法实现T1.print()

public class T1 {    public void doit(String dd, Throwable a) {        System.out.println(dd +" --- jar1");    }}


现在把整个项目编译一下,没有任何问题。

当运行test的main方法时,会发现,异常了!

Exception in thread "main" java.lang.NoSuchMethodError: com.java.jar1.T1.doit(Ljava/lang/String;Ljava/lang/Throwable;)Vat com.java.jar1.PrintUtil.print(PrintUtil.java:13)at com.java.jar.test.Test.main(Test.java:13)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:606)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

这个其实就是jarA中编译了自己的T1类,但是项目main方法运行的是在jar-test模块,jar-test模块优先加载自己的T1类,这个时候他们编译下来,都不会报错,因为jarA中找到doit()的方法,而且能够匹配上。但是最终运行的时候,程序的调用顺序是main() ->print()->T1.doit(Ljava/lang/String;Ljava/lang/Throwable;)V,那么这个时候按理说jar-test的T1.doit(Ljava/lang/String;[Ljava/lang/Object;)V也可以匹配上这样的调用。这就是问题的关键,编译后的class文件必须一一匹配上参数才行,就算是子类和父类的关系也不会匹配上,这里大家可以试一试(我试过了String 和Object的子父类关系,也是不行)。


欢迎大家有问题与我一同讨论。


测试项目下载地址:http://download.csdn.net/download/codingtu/9937638   (解压密码:codingtu)

原创粉丝点击