运行期间抛出NoSuchMethodError模拟及原因分析

运行期间抛出NoSuchMethodError

完整工程示例见:no_such_method

背景

在开发中常遇到抛出NoSuchMethodError的情景,但想不通找不到方法的问题为什么可以通过编译阶段,所以创建了三个工程来模拟出NoSuchMethod的场景进行研究。

模拟NoSuchMethodError

这里简单的复现了NoSuchMethodError出现的场景:outer有1.0和2.0两个不兼容的版本,caller依赖于outer的2.0版本,而main中又显式指出了outer的1.0版本,执行main中的主方法即报错。

下面是main的依赖树:

[INFO] com.example:main:jar:1.0-SNAPSHOT
[INFO] +- com.example:caller:jar:1.0:compile
[INFO] \- com.example:outer:jar:1.0:compile
复制代码

步骤

1. 生成1.0版本的outer

1.1 创建Painter类,其中包含一个接收String类型的print方法。

package org.example;

public class Painter {

    // 1.0
    public static void print(String name) {
        System.out.println("Hello " + name);
    }
}
复制代码

1.2 设置pom中的version为1.0

1.3 运行mvn clean install安装到本地仓库

2. 生成2.0版本的outer

2.1 更新Painter类,更新print方法,改为接收一个Integer类型的参数。

package org.example;

public class Painter {

    // 1.0
    //public static void print(String name) {
    //    System.out.println("Hello " + name);
    //}

    // 2.0
    public static void print(Integer i) {
        System.out.println("Hello NO." + i);
    }
}
复制代码

2.2 设置pom中的version为2.0

2.3 运行mvn clean install安装到本地仓库

3. 创建caller

3.1 caller依赖于2.0版本的outer,pom片段如下

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>outer</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>
复制代码

3.2 创建Caller类,调用2.0版本outer中的Painter

package com.example;

import org.example.Painter;

public class Caller {
    public static void call() {
        Painter.print(1);
    }
}
复制代码

3.3 运行mvn clean install安装到本地仓库

4. 创建main

4.1 设置pom依赖如下

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>caller</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>outer</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
复制代码

4.2 新建Main类,其中有main方法来调用Caller中的静态方法

package com.example;

public class Main {
    public static void main(String[] args) {
        Caller.call();
    }
}
复制代码

4.3 编译mvn compile -X,我们可以看到最终使用的outer版本为1.0,并且并没有报错

[DEBUG] com.example:main:jar:1.0-SNAPSHOT
[DEBUG]    com.example:caller:jar:1.0:compile
[DEBUG]    com.example:outer:jar:1.0:compile

[INFO] BUILD SUCCESS
复制代码

4.4 运行main中的主函数,抛出NoSuchMethodError,命令为mvn exec:java -Dexec.mainClass="com.example.Main" -X,结果如下

java.lang.NoSuchMethodError: org.example.Painter.print(Ljava/lang/Integer;)V
    at com.example.Caller.call (Caller.java:7)
    at com.example.Main.main (Main.java:5)
    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.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:282)
    at java.lang.Thread.run (Thread.java:748)
复制代码

原因分析

为什么编译时不报找不到方法呢?

因为依赖的jar已经编译过,无需再编译,所以不报错。或者说,编译过程中只会检查当前java文件的正确性,不会关心依赖的class的正确性。而在运行时则动态加载类,才发现找不到方法,故抛出NoSuchMethodError。

验证

将Main反编译,使用命令javap -c com.example.Main,结果如下

Compiled from "Main.java"
public class com.example.Main {
  public com.example.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method com/example/Caller.call:()V
       3: return
}

复制代码

可以看到main的编译过程中并没有涉及到对outer工程中Painter#paint方法的引用,所以不会在编译期间发现这个问题。

而如果使用1.0版本的outer作为caller的依赖,则在编译时就会报异常,编译命令:mvn clean compile,异常片段如下

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project caller: Compilation failure
[ERROR] /no_such_method/caller/src/main/java/com/example/Caller.java:[7,23] 不兼容的类型: int无法转换为java.lang.String
复制代码

转载于:https://juejin.im/post/5bd9c75bf265da39301bd712

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值