ฉันบรรจุไลบรารี Java เป็น JAR และมันกำลังขว้างหลายjava.lang.IncompatibleClassChangeError
ครั้งเมื่อฉันพยายามเรียกใช้เมธอดจากมัน ข้อผิดพลาดเหล่านี้ดูเหมือนจะสุ่ม ปัญหาประเภทใดที่ทำให้เกิดข้อผิดพลาดนี้
ฉันบรรจุไลบรารี Java เป็น JAR และมันกำลังขว้างหลายjava.lang.IncompatibleClassChangeError
ครั้งเมื่อฉันพยายามเรียกใช้เมธอดจากมัน ข้อผิดพลาดเหล่านี้ดูเหมือนจะสุ่ม ปัญหาประเภทใดที่ทำให้เกิดข้อผิดพลาดนี้
คำตอบ:
ซึ่งหมายความว่าคุณได้ทำการเปลี่ยนแปลงไบนารีที่เข้ากันไม่ได้กับไลบรารีโดยไม่ต้องคอมไพล์รหัสไคลเอนต์ใหม่ ข้อกำหนดภาษา Java §13ให้รายละเอียดการเปลี่ยนแปลงทั้งหมดอย่างเด่นชัดที่สุดเปลี่ยนstatic
เขตข้อมูล / วิธีการที่ไม่ใช่ภาคเอกชนให้เป็นstatic
หรือในทางกลับกัน
คอมไพล์รหัสไคลเอนต์กับไลบรารีใหม่และคุณควรจะไป
อัปเดต: หากคุณเผยแพร่ไลบรารีสาธารณะคุณควรหลีกเลี่ยงการเปลี่ยนแปลงไบนารีที่เข้ากันไม่ได้มากที่สุดเท่าที่จะทำได้เพื่อรักษาสิ่งที่รู้จักกันในชื่อ "ความเข้ากันได้ของไบนารีย้อนหลัง" การอัพเดตไหการพึ่งพาเพียงอย่างเดียวไม่ควรทำให้แอพพลิเคชั่นหรือบิลด์เสียหาย หากคุณจำเป็นต้องหยุดการทำงานร่วมกันของไบนารีแบบย้อนกลับขอแนะนำให้เพิ่มหมายเลขเวอร์ชันหลัก (เช่นจาก 1.xy เป็น 2.0.0) ก่อนปล่อยการเปลี่ยนแปลง
*.class
ไฟล์ทั้งหมด) แล้วทำการคอมไพล์ใหม่หรือไม่? การแก้ไขไฟล์มีผลคล้ายกัน
ไลบรารีที่จัดทำใหม่ของคุณไม่รองรับไบนารีย้อนหลัง (BC) กับเวอร์ชันเก่า ด้วยเหตุนี้ไคลเอนต์ไลบรารีที่ไม่ได้คอมไพล์ใหม่อาจมีข้อยกเว้น
นี่คือรายการการเปลี่ยนแปลงที่สมบูรณ์ใน Java Library API ที่อาจทำให้ไคลเอนต์ที่สร้างด้วยไลบรารี่เวอร์ชั่นเก่าโยน java.lang IncompatibleClassChangeErrorหากพวกเขาทำงานบนใหม่ (เช่นทำลาย BC):
หมายเหตุ : มีจำนวนมากมีข้อยกเว้นอื่น ๆ : เกิดจากการเปลี่ยนแปลงอื่น ๆ ที่เข้ากันไม่NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundErrorและAbstractMethodError
บทความที่ดีกว่าเกี่ยวกับ BC คือ"การพัฒนา API ที่ใช้ Java 2: การบรรลุความเข้ากันได้ของ API แบบไบนารี" ที่เขียนโดย Jim des Rivières
นอกจากนี้ยังมีเครื่องมืออัตโนมัติเพื่อตรวจจับการเปลี่ยนแปลงดังกล่าว:
การใช้ 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
โชคดี!
แม้ว่าคำตอบเหล่านี้จะถูกต้องทั้งหมดการแก้ไขปัญหามักจะยากขึ้น โดยทั่วไปแล้วเป็นผลมาจากสองรุ่นที่แตกต่างกันเล็กน้อยของการพึ่งพากันใน classpath และมักจะเกิดจากซูเปอร์คลาสที่แตกต่างกันกว่าเดิมที่รวบรวมมาจากการอยู่ใน classpath หรือการนำเข้าของการปิด transitive บางอย่างที่แตกต่างกัน การสร้างอินสแตนซ์และการเรียกใช้คอนสตรัคเตอร์ (หลังจากการโหลดคลาสที่ประสบความสำเร็จและการเรียกใช้ ctor คุณจะได้รับNoSuchMethodException
หรืออะไรก็ตาม)
หากพฤติกรรมปรากฏขึ้นแบบสุ่มเป็นไปได้ว่าผลลัพธ์ของโปรแกรมมัลติเธรดที่โหลดขึ้นต่อกันต่างกันของสกรรมกริยาตามรหัสที่ได้รับผลกระทบก่อน
ในการแก้ไขปัญหาเหล่านี้ให้ลองเรียกใช้ VM ด้วย-verbose
อาร์กิวเมนต์เพื่อดูคลาสที่โหลดเมื่อมีข้อยกเว้นเกิดขึ้น คุณควรเห็นข้อมูลที่น่าแปลกใจ ตัวอย่างเช่นการมีการอ้างอิงและรุ่นเดียวกันหลายชุดที่คุณไม่เคยคาดหวังหรือจะยอมรับถ้าคุณรู้ว่ามีการรวมไว้
การแก้ไขโหลที่ซ้ำกันกับ Maven ทำได้ดีที่สุดด้วยการรวมกันของmaven-dependency-pluginและmaven-enforcer-pluginภายใต้ Maven (หรือปลั๊กอินกราฟ Dependencyของ SBT จากนั้นเพิ่มขวดเหล่านั้นไปยังส่วนของ POM ระดับบนสุดของคุณหรือเป็นการนำเข้าอ้างอิง องค์ประกอบใน SBT (เพื่อลบการอ้างอิงเหล่านั้น)
โชคดี!
ฉันได้ค้นพบว่าเมื่อใช้ JNI เรียกใช้วิธีการ Java จาก C ++ ถ้าคุณส่งพารามิเตอร์ไปยังวิธีการที่เรียกใช้ Java ในลำดับที่ไม่ถูกต้องคุณจะได้รับข้อผิดพลาดนี้เมื่อคุณพยายามใช้พารามิเตอร์ภายในวิธีการที่เรียกว่า จะไม่ถูกประเภท) ในตอนแรกฉันรู้สึกประหลาดใจว่า JNI ไม่ได้ทำการตรวจสอบนี้ให้คุณซึ่งเป็นส่วนหนึ่งของการตรวจสอบลายเซ็นคลาสเมื่อคุณเรียกใช้เมธอด แต่ฉันคิดว่าพวกเขาไม่ทำการตรวจสอบแบบนี้เพราะคุณอาจผ่านพารามิเตอร์ polymorphic และ สมมติว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่
ตัวอย่างรหัส 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
ที่จริงแล้วสาเหตุของการมีไลบรารี่ที่ซ้ำกันหนึ่งอันอยู่ภายใน classpath และอีกอันรวมอยู่ในไฟล์ jar ที่อยู่ภายใน classpath
อีกสถานการณ์หนึ่งที่ข้อผิดพลาดนี้สามารถปรากฏขึ้นได้คือด้วย Emma Code Coverage
สิ่งนี้เกิดขึ้นเมื่อกำหนดวัตถุให้กับส่วนต่อประสาน ฉันเดาว่านี่เป็นสิ่งที่เกี่ยวข้องกับ Object ที่ถูกสร้างขึ้นมาและไม่สามารถใช้งานร่วมกับไบนารีได้อีกต่อไป
http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351
โชคดีที่ปัญหานี้ไม่ได้เกิดขึ้นกับ Cobertura ดังนั้นฉันจึงเพิ่ม cobertura-maven-plugin ในปลั๊กอินการรายงานของฉัน pom.xml
ฉันประสบกับปัญหานี้ในขณะที่ไม่ได้ปรับใช้และปรับใช้สงครามกับปลาแก้วอีกครั้ง โครงสร้างชั้นเรียนของฉันเป็นแบบนี้
public interface A{
}
public class AImpl implements A{
}
และมันก็เปลี่ยนเป็น
public abstract class A{
}
public class AImpl extends A{
}
หลังจากหยุดและเริ่มโดเมนใหม่ก็ทำงานได้ดี ฉันใช้ glassfish 3.1.43
ฉันมีเว็บแอปพลิเคชั่นที่ปรับใช้ได้อย่างสมบูรณ์แบบกับแมวทอมแครปในเครื่องของฉัน (8.0.20) อย่างไรก็ตามเมื่อฉันใส่ลงในสภาพแวดล้อม qa (tomcat - 8.0.20) มันยังคงให้ IncompatibleClassChangeError ฉันและมันก็บ่นว่าฉันขยายบนอินเทอร์เฟซ อินเทอร์เฟซนี้ถูกเปลี่ยนเป็นคลาสนามธรรม และฉันก็รวบรวมผู้ปกครองและชั้นเรียนเด็กและฉันยังคงได้รับปัญหาเดียวกัน ในที่สุดฉันต้องการที่จะแก้ปัญหาดังนั้นฉันเปลี่ยนรุ่นในผู้ปกครองเพื่อ x.0.1-SNAPSHOT แล้วรวบรวมทุกอย่างและตอนนี้มันทำงาน หากมีคนยังคงตีปัญหาหลังจากทำตามคำตอบที่ให้ไว้ที่นี่โปรดตรวจสอบให้แน่ใจว่ารุ่นใน pom.xml ของคุณนั้นถูกต้องเช่นกัน เปลี่ยนรุ่นเพื่อดูว่าใช้งานได้หรือไม่ ถ้าเป็นเช่นนั้นแก้ไขปัญหารุ่น
ฉันเชื่อว่าคำตอบของฉันจะเฉพาะเจาะจงของ Intellij
ฉันได้สร้างใหม่สะอาดแม้จะไปไกลเท่าที่จะลบ "ออก" และ "เป้าหมาย" dirs ด้วยตนเอง Intellij มี "แคชที่ไม่ถูกต้องและเริ่มต้นใหม่" ซึ่งบางครั้งจะล้างข้อผิดพลาดที่แปลก คราวนี้มันไม่ทำงาน รุ่นที่พึ่งพาทั้งหมดดูถูกต้องในเมนูการตั้งค่า -> โมดูล
คำตอบสุดท้ายคือการลบการพึ่งพาปัญหาจาก repo maven ท้องถิ่นของฉันด้วยตนเอง Bouncycastle รุ่นเก่าเป็นผู้ร้าย (ฉันรู้ว่าฉันเพิ่งเปลี่ยนรุ่นและนั่นจะเป็นปัญหา) และแม้ว่าเวอร์ชั่นเก่าจะไม่ปรากฏว่ามีสิ่งใดถูกสร้างขึ้นมามันแก้ปัญหาของฉันได้ ฉันใช้ Intellij เวอร์ชัน 14 แล้วอัปเกรดเป็น 15 ในระหว่างกระบวนการนี้
ในกรณีของฉันฉันพบข้อผิดพลาดนี้ด้วยวิธีนี้ pom.xml
ของโครงการที่กำหนดไว้ของฉันสองอ้างอิงและA
B
และทั้งสองA
และการB
พึ่งพาที่กำหนดไว้ในสิ่งประดิษฐ์เดียวกัน (เรียกว่าC
) แต่รุ่นที่แตกต่างกันของมัน ( C.1
และC.2
) เมื่อสิ่งนี้เกิดขึ้นสำหรับแต่ละคลาสในC
maven สามารถเลือกรุ่นหนึ่งของคลาสจากสองเวอร์ชันเท่านั้น (ในขณะที่สร้างuber-jar ) มันจะเลือกรุ่น "ใกล้ที่สุด" ขึ้นอยู่กับกฎการไกล่เกลี่ยอ้างอิงและจะส่งคำเตือน"เรามีคลาสที่ซ้ำกัน ... "หากวิธีการ / ลายเซ็นชั้นเปลี่ยนไประหว่างรุ่นก็อาจทำให้เกิดjava.lang.IncompatibleClassChangeError
ข้อยกเว้นถ้ารุ่นที่ไม่ถูกต้องคือ ใช้ที่รันไทม์
ขั้นสูง: ถ้าA
ต้องใช้ v1 ของC
และB
ต้องใช้ v2 ของC
แล้วเราต้องย้าย C
ในA
และB
's ปอมไปสู่ความขัดแย้งระดับหลีกเลี่ยง (เรามีคำเตือนระดับที่ซ้ำกัน) เมื่อมีการสร้างโครงการสุดท้ายที่ขึ้นอยู่กับทั้งสองและA
B
ในกรณีของข้อผิดพลาดปรากฏขึ้นเมื่อฉันเพิ่มห้องสมุดในใบสมัครของฉันนำไปใช้ใน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>
โปรดตรวจสอบว่ารหัสของคุณไม่ประกอบด้วยสองโครงการโมดูลที่มีชื่อคลาสและข้อกำหนดแพคเกจเดียวกัน เช่นนี้อาจเกิดขึ้นหากมีคนใช้ copy-paste เพื่อสร้างการใช้งานใหม่ของอินเทอร์เฟซตามการใช้งานก่อนหน้านี้
หากนี่เป็นบันทึกการเกิดขึ้นที่เป็นไปได้ของข้อผิดพลาดนี้ให้ทำดังนี้:
ฉันเพิ่งได้รับข้อผิดพลาดนี้ใน WAS (8.5.0.1) ระหว่างการโหลด CXF (2.6.0) ของการกำหนดค่าสปริง (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
ในกรณีนี้วิธีแก้ไขคือเปลี่ยนลำดับ classpath ของโมดูลในไฟล์ war ของฉัน นั่นคือเปิดแอ็พพลิเคชันสงครามในคอนโซล WAS ภายใต้และเลือกโมดูลไคลเอ็นต์ ในการกำหนดค่าโมดูลตั้งค่าการโหลดคลาสเป็น "พาเรนต์สุดท้าย"
พบได้ในคอนโซล WAS:
จัดทำเอกสารสถานการณ์อื่นหลังจากใช้เวลานานเกินไป
ตรวจสอบให้แน่ใจว่าคุณไม่มี jar อ้างอิงที่มีคลาสที่มีคำอธิบายประกอบ EJB อยู่
เรามีไฟล์ jar ทั่วไปที่มี@local
คำอธิบายประกอบ ชั้นเรียนนั้นถูกย้ายออกจากโปรเจ็กต์ทั่วไปและเข้าสู่โปรเจ็กต์ขวด ejb หลักของเรา โถ ejb ของเราและ jar ทั่วไปของเรานั้นถูกรวมไว้ในหู เวอร์ชันของการพึ่งพา jar ทั่วไปของเราไม่ได้รับการอัพเดต ดังนั้น 2 คลาสที่พยายามเป็นบางอย่างที่มีการเปลี่ยนแปลงที่เข้ากันไม่ได้
จากทั้งหมดที่กล่าวมา - ไม่ว่าจะด้วยเหตุผลใดก็ตามฉันกำลังทำสิ่งปลูกสร้างขนาดใหญ่และเริ่มรับสิ่งนี้ ฉันเปลี่ยนชื่อแพ็คเกจของฉันแล้ว หวังว่าจะช่วย
ด้วยเหตุผลบางอย่างยกเว้นเดียวกันยังจะถูกโยนทิ้งเมื่อใช้ JNI และผ่านอาร์กิวเมนต์ jclass แทน jobject 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 เซนต์ของฉันหากคุณใช้สกาล่าและ sbt และการบันทึกสกาล่าเป็นการพึ่งพาซึ่งอาจเกิดขึ้นได้เนื่องจากเวอร์ชันก่อนหน้าของการบันทึกล็อกสกาล่ามีชื่อ ชื่อที่นำไปสู่ข้อผิดพลาดรันไทม์ขณะเรียกใช้แอปพลิเคชันสกาล่า
สาเหตุเพิ่มเติมของปัญหานี้คือถ้าคุณInstant Run
เปิดใช้งานสำหรับ Android Studio
Instant Run
หากคุณพบว่าคุณเริ่มได้รับข้อผิดพลาดนี้ปิด
Instant Run
ปรับเปลี่ยนสิ่งต่าง ๆ จำนวนมากในระหว่างการพัฒนาเพื่อทำให้การอัปเดตแอปที่ทำงานอยู่ของคุณเร็วขึ้น ดังนั้นจึงเรียกใช้ทันที เมื่อใช้งานได้จริงมันมีประโยชน์จริงๆ อย่างไรก็ตามเมื่อปัญหาเช่นการประท้วงนี้สิ่งที่ดีที่สุดที่ต้องทำคือการปิดInstant Run
จนกว่า Android Studio รุ่นถัดไปจะออก