我将Java库打包为JAR,当我尝试从中调用方法时,它会抛出许多java.lang.IncompatibleClassChangeError
s。这些错误似乎是随机出现的。什么类型的问题可能导致此错误?
这意味着您已对库进行了一些不兼容的二进制更改,而无需重新编译客户端代码。 Java Language Specification §13详述了所有这些变化,最突出的是,将非static
非私人领域/方法改为static
,反之亦然。
针对新库重新编译客户端代码,您应该很高兴。
更新:如果您发布公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的“二进制向后兼容性”。理想情况下,单独更新依赖关系jar不应该破坏应用程序或构建。如果你必须打破二进制向后兼容性,那么在发布更改之前,recommended会增加主要版本号(例如从1.x.y到2.0.0)。
就我而言,我以这种方式遇到了这个错误。我项目的pom.xml
定义了两个依赖项A
和B
。 A
和B
都定义了对相同工件(称为C
)的依赖,但它的不同版本(C.1
和C.2
)。当发生这种情况时,对于C
中的每个类,maven只能从两个版本中选择一个版本的类(同时构建一个uber-jar)。它将根据其dependency mediation规则选择“最近”版本并输出警告"We have a duplicate class..."如果方法/类签名在版本之间发生变化,如果在运行时使用了不正确的版本,则会导致java.lang.IncompatibleClassChangeError
异常。
高级:如果A
必须使用C
的v1而且B
必须使用C
的v2,那么我们必须在relocate和C
的poms中使用A
B
以避免类冲突(我们有一个重复的类警告)在构建依赖于A
和B
的最终项目时com.nimbusds
。
在我的情况下,当我在Websphere 8.5
上部署的应用程序中添加<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>
库时出现错误。
发生以下异常:
引起:java.lang.IncompatibleClassChangeError:org.objectweb.asm.AnnotationVisitor
解决方案是从库中排除asm jar:
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
请检查您的代码是否包含两个具有相同类名和包定义的模块项目。例如,如果有人使用复制粘贴来创建基于先前实现的接口的新实现,则可能发生这种情况。
如果这是可能出现此错误的记录,则:
在Spring(3.1.1_release)配置的CXF(2.6.0)加载期间,我刚刚在WAS(8.5.0.1)上遇到此错误,其中BeanInstantiationException卷起了一个CXF ExtensionException,卷起了IncompatibleClassChangeError。以下代码段显示了堆栈跟踪的要点:
@local
在这种情况下,解决方案是更改war文件中模块的类路径顺序。也就是说,在WAS控制台下打开war应用程序并选择客户端模块。在模块配置中,将类加载设置为“父最后一个”。
这可以在WAS控制台中找到:
在刻录太多时间之后记录另一个场景。
确保您没有依赖jar,该jar具有带有EJB注释的类。
我们有一个常见的jar文件,它有一个Call*Method()
注释。该课程后来被移出该共同项目并进入我们的主要ejb jar项目。我们的ejb jar和我们常见的jar都捆绑在耳朵里。我们常见的jar依赖项的版本没有更新。因此,有两个类试图成为具有不兼容变化的东西。
以上所有 - 无论出于什么原因,我正在做一些大型重构并开始得到这个。我重命名了我的界面所在的包,并清除了它。希望有所帮助。
出于某种原因,在调用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;
}
时使用JNI并传递jclass参数而不是jobject时也会抛出相同的异常。
这类似于Ogre Psalm33的答案。
java.lang.IncompatibleClassChangeError
我知道在被问到这个问题5年后回答这个问题有点晚了,但这是搜索Instant Run
时的热门搜索之一,所以我想记录这个特例。
加上我的2美分。如果你使用scala和sbt以及scala-logging作为依赖项;那么这可能发生,因为scala-logging的早期版本的名称为scala-logging-api.So;本质上,依赖项解析不会因为不同而发生启动scala应用程序时导致运行时错误的名称。
此问题的另一个原因是,如果您为Android Studio启用了Instant Run
。
如果您发现开始出现此错误,请关闭Instant Run
。
Instant Run
在开发过程中修改了大量内容,以便更快地为正在运行的应用程序提供更新。因此即时运行。当它工作时,它真的很有用。但是,当这样的问题发生时,最好的办法是关闭qazxswpoi,直到Android Studio的下一个版本发布。
您新打包的库与旧版本不是后向二进制兼容(BC)。因此,一些未重新编译的库客户端可能会抛出异常。
这是Java库API中的更改的完整列表,如果它们在新的版本上运行(即中断BC),则可能导致使用旧版本库构建的客户端抛出java.lang.IncompatibleClassChangeError:
注意:由于其他不兼容的更改而导致许多其他异常:NoSuchFieldError,NoSuchMethodError,IllegalAccessError,InstantiationError,VerifyError,NoClassDefFoundError和AbstractMethodError。
有关BC的更好的论文是由JimdesRiviè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文件中。
可能出现此错误的另一种情况是Emma Code Coverage。
将对象分配给接口时会发生这种情况。我想这与被检测的对象有关,而不再与二进制兼容。
http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351
幸运的是,这个问题不会发生在Cobertura上,所以我在我的pom.xml的报告插件中添加了cobertura-maven-plugin
我在使用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中的版本也正确无误。更改版本以查看是否有效。如果是这样,那么修复版本问题。
我相信,我的答案将是Intellij具体的。
我已经重建了干净,甚至可以手动删除“out”和“target”dirs。 Intellij有一个“无效的缓存和重启”,有时会清除奇怪的错误。这次它没用。在项目设置 - >模块菜单中,依赖版本看起来都是正确的。
最后的答案是从我当地的maven repo手动删除我的问题依赖项。旧版本的bouncycastle是罪魁祸首(我知道我刚刚更改了版本,这将是问题)虽然旧版本没有出现在正在构建的地方,但它解决了我的问题。我正在使用intellij版本14,然后在此过程中升级到15。