如我遇到的问题是:java.lang.IncompatibleClassChangeError: Found class com.**.redis.RedisService, but interface was expected.
其实很明显的告诉了我需要一个RedisService Interface类,但RedisService确是Class. 所以发生了这个报错。
(出现这种问题,八成的原因是因为依赖版本冲突,当然我这个也是,只不过我遇到的是我自己写的依赖包。)
建议多去stackoverflow逛逛, 答案质量比较高,下面是stackoverflow的回答,看完这个我相信你就有答案了。
https://stackoverflow.com/questions/1980452/what-causes-java-lang-incompatibleclasschangeerror
贴出其中给一部分:
PS: 自己翻译吧, 当遇到未知问题还是要有一定的翻译能力
Your newly packaged library is not backward binary compatible (BC) with old version. For this reason some of the library clients that are not recompiled may throw the exception.
This is a complete list of changes in Java library API that may cause clients built with an old version of the library to throw java.lang.IncompatibleClassChangeError if they run on a new one (i.e. breaking BC):
1. Non-final field become static,
2. Non-constant field become non-static,
3. Class become interface,
4. Interface become class,
if you add a new field to class/interface (or add new super-class/super-interface) then a static field from a super-interface of a client class C may hide an added field (with the same name) inherited from the super-class of C (very rare case).
Note: There are many other exceptions caused by other incompatible changes: NoSuchFieldError, NoSuchMethodError, IllegalAccessError, InstantiationError, VerifyError, NoClassDefFoundError and AbstractMethodError.
The better paper about BC is "Evolving Java-based APIs 2: Achieving API Binary Compatibility" written by Jim des Rivières.
其中链接: Evolving Java-based APIs
排查方式
这里针对我遇到的异常来做个展示,首先要做的当然是先解决依赖冲突的问题(说不定解决完你的问题就解决了),这里不演示。
- 首先根据异常定位到具体的代码位置,找到对应的包
at com.apicrypto.util.NeedCryptoUtils.getEncryptKey(NeedCryptoUtils.java:113)
java.lang.IncompatibleClassChangeError: Found class com.redis.RedisService, but interface was expected
at com.apicrypto.util.NeedCryptoUtils.getEncryptKey(NeedCryptoUtils.java:113)
at com.apicrypto.interceptor.DecryptFilter.doFilter(DecryptFilter.java:56)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at com.truckerpath.apicrypto.interceptor.InstallationIdValidateFilter.doFilter(InstallationIdValidateFilter.java:50)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at dev.akkinoc.spring.boot.logback.access.tomcat.LogbackAccessTomcatValve.invoke(LogbackAccessTomcatValve.kt:55)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:735)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:1583)
-
找到对应的位置后,解压jar包。利用javap命令查看此NeedCryptoUtils.class的字节码指令。
javap -c -v NeedCryptoUtils.class
找到对应的方法,
15: invokeinterface #69, 3
// InterfaceMethod com/redis/RedisService.get:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
这里可以看出,编译包中的字节码是invokeinterface
(interface.method()指令,而当我反编译RedisService类的最新包时,编译出来是invokevirtual
(class.method()),这里就和报错信息对上了Found class com.redis.RedisService, but interface was expected
编译指令参考:https://zhuanlan.zhihu.com/p/356847335public static java.lang.String getEncryptKey(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/String; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=3, args_size=1 0: aload_0 1: invokestatic #59 // Method org/apache/commons/lang3/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z 4: ifeq 9 7: aconst_null 8: areturn 9: getstatic #7 // Field redisService:Lcom/redis/RedisService; 12: ldc #67 14: aload_0 15: invokeinterface #69, 3 // InterfaceMethod com/redis/RedisService.get:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 20: astore_1 21: aload_1 22: invokestatic #75 // Method org/apache/commons/lang3/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z 25: ifeq 88 28: getstatic #7 // Field redisService:Lcom/redis/RedisService; 31: aload_0 32: invokedynamic #78, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String; 37: aload_1 38: invokeinterface #82, 3 // InterfaceMethod com/redis/RedisService.incr:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Long;```
-
直接1中的包中,引用的RedisService.class版本,然后对比你项目解决完冲突后的RedisService.class版本,会发现1包中的RedisService是个
public interface RedisService{}
, 而最新包RedisService,却是public class RedisService{}
, 尽管两个RedisService的方法都一样,使用方式也都一样,但编译指令不一样。 接下来你就保持版本一致就好了。 -
我的解决方案是将RedisService改为了
public interface RedisService{}
,稍微改了一下实现方式,因为是我对RedisService做了升级,底层包增加了Redis读写分离的功能,但写的过程中以为只要方法都有,使用方式一样就可以适配老版本,没有考虑编译指令的问题。所以遇到此问题后,就将RedisService改回接口了。然后重新打包编译,解决问题。