我将 Java 库打包为 JAR,java.lang.IncompatibleClassChangeError
当我尝试从中调用方法时,它会抛出很多 s。这些错误似乎是随机出现的。什么样的问题可能导致此错误?
18 回答
这意味着您在没有重新编译客户端代码的情况下对库进行了一些不兼容的二进制更改。 Java 语言规范 §13详细介绍了所有此类更改,最突出的是,将static
非非私有字段/方法更改为static
,反之亦然。
针对新库重新编译客户端代码,您应该一切顺利。
更新:如果您发布公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的“二进制向后兼容性”。理想情况下,单独更新依赖项 jar 不应破坏应用程序或构建。如果您确实必须破坏二进制向后兼容性,建议在发布更改之前增加主要版本号(例如从 1.xy 到 2.0.0)。
您新打包的库与旧版本不向后二进制兼容(BC)。由于这个原因,一些未重新编译的库客户端可能会抛出异常。
这是 Java 库 API 更改的完整列表,这些更改可能会导致使用旧版本库构建的客户端抛出 java.lang。IncompatibleClassChangeError如果它们在新的上运行(即破坏 BC):
- 非最终字段变为静态,
- 非常量字段变为非静态,
- 类变成接口,
- 接口变成类,
- 如果您向类/接口添加新字段(或添加新的超类/超接口),则来自客户端类 C 的超接口的静态字段可能会隐藏从C 的超类(非常罕见的情况)。
注意:还有许多其他不兼容更改导致的异常: NoSuchFieldError、NoSuchMethodError、IllegalAccessError、InstantiationError、VerifyError、NoClassDefFoundError和AbstractMethodError。
关于 BC 的更好的论文是Jim des Rivières 撰写的“Evolving Java-based APIs 2: Achieving API Binary Compatibility” 。
还有一些自动工具可以检测此类更改:
为您的库使用 japi-compliance-checker:
japi-compliance-checker OLD.jar NEW.jar
clirr工具的使用:
java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar
祝你好运!
虽然这些答案都是正确的,但解决问题往往更加困难。它通常是类路径上相同依赖项的两个稍微不同版本的结果,并且几乎总是由与最初编译时在类路径上不同的超类或传递闭包的某些导入不同引起的,但通常在类实例化和构造函数调用。(在成功的类加载和 ctor 调用之后,你会得到NoSuchMethodException
什么。)
如果行为出现随机,则可能是多线程程序类加载不同的传递依赖项的结果,该依赖项基于首先命中的代码。
要解决这些问题,请尝试使用-verbose
作为参数启动 VM,然后查看发生异常时正在加载的类。您应该会看到一些令人惊讶的信息。例如,拥有相同依赖项和版本的多个副本,如果您知道它们被包含在内,您从未期望或会接受它们。
使用 Maven 解决重复的 jar 最好结合 Maven 下的maven-dependency-plugin和maven-enforcer-plugin(或 SBT 的Dependency Graph Plugin,然后将这些 jar 添加到顶级 POM 的一部分或作为导入的依赖项SBT 中的元素(以删除这些依赖项)。
祝你好运!
我还发现,当使用 JNI 从 C++ 调用 Java 方法时,如果以错误的顺序将参数传递给被调用的 Java 方法,则在尝试使用被调用方法中的参数时会出现此错误(因为它们不会是正确的类型)。我最初感到惊讶的是,当您调用该方法时,JNI 不会在类签名检查中为您执行此检查,但我认为他们不会进行此类检查,因为您可能正在传递多态参数并且他们必须假设你知道你在做什么。
示例 C++ JNI 代码:
void invokeFooDoSomething() {
jobject javaFred = FredFactory::getFred(); // Get a Fred jobject
jobject javaFoo = FooFactory::getFoo(); // Get a Foo jobject
jobject javaBar = FooFactory::getBar(); // Get a Bar jobject
jmethodID methodID = getDoSomethingMethodId() // Get the JNI Method ID
jniEnv->CallVoidMethod(javaFoo,
methodID,
javaFred, // Woops! I switched the Fred and Bar parameters!
javaBar);
// << Insert error handling code here to discover the JNI Exception >>
// ... This is where the IncompatibleClassChangeError will show up.
}
示例 Java 代码:
class Bar { ... }
class Fred {
public int size() { ... }
}
class Foo {
public void doSomething(Fred aFred, Bar anotherObject) {
if (name.size() > 0) { // Will throw a cryptic java.lang.IncompatibleClassChangeError
// Do some stuff...
}
}
}
我遇到了同样的问题,后来我发现我在 Java 版本 1.4 上运行应用程序,而应用程序是在版本 6 上编译的。
实际上,原因是有一个重复的库,一个位于类路径中,另一个包含在位于类路径中的 jar 文件中。
在我的情况下,当我com.nimbusds
在部署的应用程序中添加库时出现错误Websphere 8.5
。
发生以下异常:
引起:java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor
解决方案是从库中排除 asm jar:
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>5.1</version>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
我在取消部署和重新部署与 glassfish 的战争时遇到了这个问题。我的班级结构是这样的,
public interface A{
}
public class AImpl implements A{
}
它被改为
public abstract class A{
}
public class AImpl extends A{
}
停止并重新启动域后,它运行良好。我正在使用 glassfish 3.1.43
我有一个 Web 应用程序可以完美地部署在我本地机器的 tomcat(8.0.20) 上。但是,当我将它放入 qa 环境(tomcat - 8.0.20)时,它不断给我 IncompatibleClassChangeError 并且它抱怨我正在扩展接口。此接口已更改为抽象类。我编译了父类和子类,但我仍然遇到同样的问题。最后,我想调试,所以,我将父版本的版本更改为 x.0.1-SNAPSHOT,然后编译所有内容,现在它可以工作了。如果有人在按照此处给出的答案后仍然遇到问题,请确保您的 pom.xml 中的版本也是正确的。更改版本以查看是否有效。如果是这样,则修复版本问题。
可能出现此错误的另一种情况是 Emma 代码覆盖率。
将对象分配给接口时会发生这种情况。我想这与被检测的对象有关,不再兼容二进制。
http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351
幸运的是 Cobertura 不会出现这个问题,所以我在 pom.xml 的报告插件中添加了 cobertura-maven-plugin
就我而言,我以这种方式遇到了这个错误。pom.xml
我的项目定义了两个依赖项A
和B
. 并且两者都A
定义了对同一工件(称为它)但它的不同版本(和)的B
依赖。发生这种情况时,对于 maven 中的每个类,只能从两个版本中选择一个版本的类(在构建uber-jar时)。它将根据其依赖中介规则选择“最近”的版本,并输出警告“我们有一个重复的类... ”在运行时使用。C
C.1
C.2
C
java.lang.IncompatibleClassChangeError
高级:如果A
必须使用 v1 ofC
并且B
必须使用 v2 of ,那么在构建依赖于and的最终项目时C
,我们必须重新定位 C
到A
and的 pom 中以避免类冲突(我们有一个重复的类警告) 。B
A
B
我相信,我的回答将是 Intellij 特有的。
我已经重建干净,甚至手动删除“out”和“target”目录。Intellij 有一个“使缓存无效并重新启动”,有时可以清除奇怪的错误。这一次没有奏效。依赖版本在项目设置->模块菜单中看起来都是正确的。
最后的答案是从我的本地 maven repo 中手动删除我的问题依赖项。一个旧版本的 bouncycastle 是罪魁祸首(我知道我刚刚更改了版本,这就是问题所在),虽然旧版本在构建的地方没有出现,但它解决了我的问题。我使用的是 intellij 版本 14,然后在此过程中升级到 15。
在花费太多时间后记录另一个场景。
确保您没有一个依赖项 jar,该依赖项 jar 具有一个带有 EJB 注释的类。
我们有一个带有@local
注释的通用 jar 文件。该类后来从那个公共项目中移出并进入我们的主 ejb jar 项目。我们的 ejb jar 和我们的普通 jar 都捆绑在一个耳朵里。我们常见的 jar 依赖的版本没有更新。因此 2 个类试图成为具有不兼容更改的东西。
如果这是此错误可能发生的记录,则:
我刚刚在 WAS (8.5.0.1) 上收到此错误,在 CXF (2.6.0) 加载 spring (3.1.1_release) 配置期间,BeanInstantiationException 卷起 CXF ExtensionException,卷起 IncompatibleClassChangeError。以下片段显示了堆栈跟踪的要点:
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.apache.cxf.bus.spring.SpringBus]: Constructor threw exception; nested exception is org.apache.cxf.bus.extension.ExtensionException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
... 116 more
Caused by: org.apache.cxf.bus.extension.ExtensionException
at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:167)
at org.apache.cxf.bus.extension.Extension.getClassObject(Extension.java:179)
at org.apache.cxf.bus.extension.ExtensionManagerImpl.activateAllByType(ExtensionManagerImpl.java:138)
at org.apache.cxf.bus.extension.ExtensionManagerBus.<init>(ExtensionManagerBus.java:131)
[etc...]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 118 more
Caused by: java.lang.IncompatibleClassChangeError:
org.apache.neethi.AssertionBuilderFactory
at java.lang.ClassLoader.defineClassImpl(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:284)
[etc...]
at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
at java.lang.ClassLoader.loadClass(ClassLoader.java:658)
at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:163)
... 128 more
在这种情况下,解决方案是在我的 war 文件中更改模块的类路径顺序。也就是说,在 WAS 控制台下打开 war 应用程序并选择客户端模块。在模块配置中,将 class-loading 设置为“parent last”。
这可以在 WAS 控制台中找到:
- 应用程序 -> 应用程序类型 -> WebSphere 企业应用程序
- 单击代表您的应用程序的链接(战争)
- 单击“模块”部分下的“管理模块”
- 单击底层模块的链接
- 将“类加载器顺序”更改为“(最后一个父级)”。
请检查您的代码是否不包含具有相同类名和包定义的两个模块项目。例如,如果有人使用复制粘贴来创建基于先前实现的新接口实现,则可能会发生这种情况。
以上所有——无论出于何种原因,我都在做一些大的重构并开始得到这个。我重命名了我的界面所在的包并清除了它。希望有帮助。
此问题的另一个原因是您是否Instant Run
启用了 Android Studio。
修复
如果您发现您开始收到此错误,请关闭Instant Run
.
- Android Studio 主要设置
- 构建、执行、部署
- 即时运行
- 取消勾选“启用即时运行...”
为什么
Instant Run
在开发过程中修改了大量的东西,以便更快地为您正在运行的应用程序提供更新。因此即时运行。当它起作用时,它真的很有用。但是,当出现此类问题时,最好的办法是关闭,Instant Run
直到下一个版本的 Android Studio 发布。
由于某种原因,在使用 JNI 并在调用Call*Method()
.
这类似于 Ogre Psalm33 的答案。
void example(JNIEnv *env, jobject inJavaList) {
jclass class_List = env->FindClass("java/util/List");
jmethodID method_size = env->GetMethodID(class_List, "size", "()I");
long size = env->CallIntMethod(class_List, method_size); // should be passing 'inJavaList' instead of 'class_List'
std::cout << "LIST SIZE " << size << std::endl;
}
我知道在被问到 5 年后回答这个问题有点晚了,但这是搜索时最热门的问题之一,java.lang.IncompatibleClassChangeError
所以我想记录这个特殊情况。
添加我的 2 美分。如果您使用 scala 和 sbt 和 scala-logging 作为依赖项;那么这可能会发生,因为 scala-logging 的早期版本具有名称 scala-logging-api.So;本质上,由于不同的依赖关系解决方案不会发生启动 scala 应用程序时导致运行时错误的名称。