从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、编译器在方法调用前就完全确定一个方法的一一匹配
下面看jar-test模块所引用的jar包 jar1(对应上面说的jatA)
最后看jarA中对应jarB(version:1.0)对应的类方法实现T1.print()
这个其实就是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的子父类关系,也是不行)。
先来讲一下我的项目关键的结构
我的项目引用了两个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)
阅读全文
1 0
- 从NoSuchMethodError看jvm编译和class加载方式
- 从finally看class编译字节码
- JVM类加载-从ClassLoader源码看双亲委托模型
- jvm加载class
- JVM加载Class过程
- jvm加载class原理
- JVM class加载机制
- jvm-class加载机制
- 从面试题看问题之JVM和内存
- jvm加载class文件--笔记
- jvm加载class类机制
- JVM 加载.class的过程
- jvm是如何加载class
- JVM类加载Class文件
- JVM加载class的原理
- 从设计层面看abstract class和interface
- 从语法定义层面看abstract class 和 interface
- 从JVM Instructions看Java
- Python基础02 基本数据类型
- minetest源码解析五:IGameDef、ItemDefManager、NodeDefManager类介绍
- Unable to instantiate applicatio
- mongo 搭建 replica set
- Unity_往复运动_057
- 从NoSuchMethodError看jvm编译和class加载方式
- nmcli的设备和连接
- HDU4162 Shape Number(最小表示法)
- NOIP训练测试3(2017081601)
- Java实现双向链表/双端链表
- 工厂解耦
- Spring乱码问题解决方案
- 配置Service.xml
- Squid代理服务器