前言
// 四刷天府绿道
呵呵 在前面文章中 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 {// Test00MultiClasspathSaveClasspublic 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.classLast modified Oct 24, 2021; size 800 bytesMD5 checksum 56f3e1f6bc90419599e855b9992cd33eCompiled from "Test00MultiClasspathSaveClass.java"
public class com.hx.test.Test00MultiClasspathSaveClassminor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 10: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/hx/test/Test00MultiClasspathSaveClass;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=2, args_size=10: new #2 // class com/hx/test/UserServiceFactory3: dup4: invokespecial #3 // Method com/hx/test/UserServiceFactory."<init>":()V7: invokevirtual #4 // Method com/hx/test/UserServiceFactory.newUserService:()Lcom/hx/test/UserService;10: astore_111: aload_112: ldc #5 // String xxx14: ldc #6 // String xx16: invokeinterface #7, 3 // InterfaceMethod com/hx/test/UserService.updateUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;21: pop22: aload_123: ldc #5 // String xxx25: ldc #6 // String xx27: invokeinterface #8, 3 // InterfaceMethod com/hx/test/UserService.removeUser:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;32: pop33: returnLineNumberTable:line 15: 0line 16: 11line 17: 22line 19: 33LocalVariableTable:Start Length Slot Name Signature0 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 不兼容, 导致服务启动失败
完