43 多个相同限定名类型同时存在导致的继承结构混乱的情况

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了43 多个相同限定名类型同时存在导致的继承结构混乱的情况相关的知识,希望对你有一定的参考价值。

前言

 // 四刷天府绿道 

呵呵 在前面文章中 jetty-runner:jar:9.3.20 和 tomcat-embed-core-8.5.29 的 JarScannerCallback 不兼容, 导致服务启动失败 

提到了这样的一个问题 

我们再看一下这里的 callback 的接口, jetty-runner 的这个对象里面是没有 void scan(Jar jar, String webappPath, boolean isWebapp), 抛出了异常 

当然 假设 jetty-runner 里面 JarScannerCallback 有这个方法, 又会不会出现问题呢?, 我们单开一篇文章讨论 

呵呵 这里就是来看这里的问题 

当然本文需要对 invokeXX 指令的执行过程有一定的了解, 如下文章 也许有帮助   

方法调用的流程(invokestatic为例)

invoke static/special/virtual/interface

测试用例

UserService 接口 

package com.hx.test;

/**
 * UserService
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-10-24 15:47
 */
public interface UserService 

    String updateUser(String username, String password);

    String removeUser(String username, String password);


测试用例

package com.hx.test;

/**
 * Test00MultiClasspathSaveClass
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-10-24 15:46
 */
public class Test00MultiClasspathSaveClass 

    // Test00MultiClasspathSaveClass
    public static void main(String[] args) 

        UserService userService = new UserServiceFactory().newUserService();
        userService.updateUser("xxx", "xx");
        userService.removeUser("xxx", "xx");

    


另外还需要另外一个包 : UserAddUpdate.jar 

里面存放的是 另外的一个 UserService 和 UserServiceImpl 

package com.hx.test;

public interface UserService 
    String addUser(String var1, String var2);

    String updateUser(String var1, String var2);

package com.hx.test;

public class UserServiceImpl implements UserService 
    public UserServiceImpl() 
    

    public String addUser(String username, String password) 
        System.out.println("UserServiceImpl[addUser/updateUser] -> addUser");
        return username;
    

    public String updateUser(String username, String password) 
        System.out.println("UserServiceImpl[addUser/updateUser] -> updateUser");
        return username;
    

package com.hx.test;

public class UserServiceFactory 
    public UserServiceFactory() 
    

    public UserService newUserService() 
        return new UserServiceImpl();
    

然后执行 用例, 会发现报错如下  

粘贴一下 主要测试用例的字节码信息 

master:classes jerry$ javap -v -c com/hx/test/Test00MultiClasspathSaveClass.class 
Classfile /Users/jerry/IdeaProjects/HXCase/target/classes/com/hx/test/Test00MultiClasspathSaveClass.class
  Last modified Oct 24, 2021; size 800 bytes
  MD5 checksum 56f3e1f6bc90419599e855b9992cd33e
  Compiled from "Test00MultiClasspathSaveClass.java"
public class com.hx.test.Test00MultiClasspathSaveClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#26        // java/lang/Object."<init>":()V
   #2 = Class              #27            // com/hx/test/UserServiceFactory
   #3 = Methodref          #2.#26         // com/hx/test/UserServiceFactory."<init>":()V
   #4 = Methodref          #2.#28         // com/hx/test/UserServiceFactory.newUserService:()Lcom/hx/test/UserService;
   #5 = String             #29            // xxx
   #6 = String             #30            // xx
   #7 = InterfaceMethodref #31.#32        // com/hx/test/UserService.updateUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
   #8 = InterfaceMethodref #31.#33        // com/hx/test/UserService.removeUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
   #9 = Class              #34            // com/hx/test/Test00MultiClasspathSaveClass
  #10 = Class              #35            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/hx/test/Test00MultiClasspathSaveClass;
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               userService
  #23 = Utf8               Lcom/hx/test/UserService;
  #24 = Utf8               SourceFile
  #25 = Utf8               Test00MultiClasspathSaveClass.java
  #26 = NameAndType        #11:#12        // "<init>":()V
  #27 = Utf8               com/hx/test/UserServiceFactory
  #28 = NameAndType        #36:#37        // newUserService:()Lcom/hx/test/UserService;
  #29 = Utf8               xxx
  #30 = Utf8               xx
  #31 = Class              #38            // com/hx/test/UserService
  #32 = NameAndType        #39:#40        // updateUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #33 = NameAndType        #41:#40        // removeUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #34 = Utf8               com/hx/test/Test00MultiClasspathSaveClass
  #35 = Utf8               java/lang/Object
  #36 = Utf8               newUserService
  #37 = Utf8               ()Lcom/hx/test/UserService;
  #38 = Utf8               com/hx/test/UserService
  #39 = Utf8               updateUser
  #40 = Utf8               (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #41 = Utf8               removeUser

  public com.hx.test.Test00MultiClasspathSaveClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/hx/test/Test00MultiClasspathSaveClass;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class com/hx/test/UserServiceFactory
         3: dup
         4: invokespecial #3                  // Method com/hx/test/UserServiceFactory."<init>":()V
         7: invokevirtual #4                  // Method com/hx/test/UserServiceFactory.newUserService:()Lcom/hx/test/UserService;
        10: astore_1
        11: aload_1
        12: ldc           #5                  // String xxx
        14: ldc           #6                  // String xx
        16: invokeinterface #7,  3            // InterfaceMethod com/hx/test/UserService.updateUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        21: pop
        22: aload_1
        23: ldc           #5                  // String xxx
        25: ldc           #6                  // String xx
        27: invokeinterface #8,  3            // InterfaceMethod com/hx/test/UserService.removeUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        32: pop
        33: return
      LineNumberTable:
        line 15: 0
        line 16: 11
        line 17: 22
        line 19: 33
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      34     0  args   [Ljava/lang/String;
           11      23     1 userService   Lcom/hx/test/UserService;

SourceFile: "Test00MultiClasspathSaveClass.java"

问题的分析

以上用例执行 classpath 为 : /Users/jerry/IdeaProjects/HXCase/target/classes:/Users/jerry/IdeaProjects/HXCase/target/artifacts/UserAddUpdate/UserAddUpdate.jar

大致可以理出程序中使用的 UserService 是 updateUser + removeUser 所在的 UserService, 所以 用例能够正常编译通过 

然后 new UserServiceFactory().newUserService() 获取到的实例为 UserServiceImpl, 继承自 addUser + updateUser 所在的 UserService 

如果你看过 上面提到的两篇文章, 应该能够想到这个结果 是为什么 

Test00MultiClasspathSaveClass 中创建了 UserServiceImpl 的实例, 并调用了 updateUser + removeUser 所在的 UserService 的 updateUser 方法 

"invokeinterface #7, 3" 对应的是 Test00MultiClasspathSaveClass 中常量池中第七个元素, 是一个 InterfaceMethodRef 

在 invokeinterface 的时候, 发现对应的 cacheEntry 尚未被解析, 于是开始解析 #7, 将符号引用替换为直接引用(解析是通过 name 和 signature 来进行匹配的) 

解析 #7 的时候, updateUser + removeUser 所在的 UserService 中有 updateUser(String, String), 并且 UserServiceImpl 中有 updateUser(String, String), 解析成功, 然后调用的是 UserServiceImpl 的 updateUser(String, String) 

同理解析 #8 的时候 updateUser + removeUser 所在的 UserService 中有 removeUser(String, String), 并且 UserServiceImpl 中没有有 removeUser(String, String) 

然后 vm 的理解是 UserServiceImpl 实现了 updateUser + removeUser 所在的 UserService 的接口, 但是又没有重写 removeUser(String, String) 方法, 抛出了 AbstractMethodError 

调整classpath的顺序

假设我们吧 UserAddUpdate.jar 的依赖放在前面, 那么我们 UserService 是 addUser + updateUser 的 UserService, UserServiceImpl 继承自 addUser + updateUser 的 UserService 

但是 Test00MultiClasspathSaveClass 中还有一个 removeUser(String, String) 的一个 invokeinterface, 看一下 会有怎么样的效果 

invokeinterface #7 和上面同理 

同理解析 #8 的时候 addUser + updateUser 所在的 UserService 中没有有 removeUser(String, String) 

然后有需要调用 addUser + updateUser 所在的 UserService 的 removeUser(String, String), 因此 直接抛出了 NoSuchMethodError 

cmd 中调用 Test00MultiClasspathSaveClass, 调整 classpath 的不同的效果 

master:UserAddUpdate jerry$ java -classpath "/Users/jerry/IdeaProjects/HXCase/target/classes:/Users/jerry/IdeaProjects/HXCase/target/artifacts/UserAddUpdate/UserAddUpdate.jar" com.hx.test.Test00MultiClasspathSaveClass
UserServiceImpl[addUser/updateUser] -> updateUser
Exception in thread "main" java.lang.AbstractMethodError: com.hx.test.UserServiceImpl.removeUser(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
	at com.hx.test.Test00MultiClasspathSaveClass.main(Test00MultiClasspathSaveClass.java:17)

master:UserAddUpdate jerry$ java -classpath "/Users/jerry/IdeaProjects/HXCase/target/artifacts/UserAddUpdate/UserAddUpdate.jar:/Users/jerry/IdeaProjects/HXCase/target/classes" com.hx.test.Test00MultiClasspathSaveClass
UserServiceImpl[addUser/updateUser] -> updateUser
Exception in thread "main" java.lang.NoSuchMethodError: com.hx.test.UserService.removeUser(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
	at com.hx.test.Test00MultiClasspathSaveClass.main(Test00MultiClasspathSaveClass.java:17)

本问题在一般的新项目中不会出现, 但是 在一些老项目, 或者 依赖相当多, 相当杂的情况下 是有可能出现的, 遇到问题的时候不要慌, 看一下本文的理解 

参考 

方法调用的流程(invokestatic为例)

invoke static/special/virtual/interface

jetty-runner:jar:9.3.20 和 tomcat-embed-core-8.5.29 的 JarScannerCallback 不兼容, 导致服务启动失败 

以上是关于43 多个相同限定名类型同时存在导致的继承结构混乱的情况的主要内容,如果未能解决你的问题,请参考以下文章

在C# .net中,多个客户同时写入数据会不会导致数据混乱?

java中重载,继承,重写和多态的区别

我的指针导致“多次使用相同类型的限定符”警告?

40 classpath中存在多个jar存在同限定名的class classloader会如何加载

40 classpath中存在多个jar存在同限定名的class classloader会如何加载

基类和子类函数名相同且参数类型一样,基类函数如何被覆盖,还存在吗?存在何处?请详细回答,谢谢~~~~