Tôi đang đóng gói một thư viện Java dưới dạng JAR và nó sẽ ném nhiều java.lang.IncompatibleClassChangeError
s khi tôi cố gắng gọi các phương thức từ nó. Những lỗi này dường như xuất hiện ngẫu nhiên. Những loại vấn đề có thể gây ra lỗi này?
Tôi đang đóng gói một thư viện Java dưới dạng JAR và nó sẽ ném nhiều java.lang.IncompatibleClassChangeError
s khi tôi cố gắng gọi các phương thức từ nó. Những lỗi này dường như xuất hiện ngẫu nhiên. Những loại vấn đề có thể gây ra lỗi này?
Câu trả lời:
Điều này có nghĩa là bạn đã thực hiện một số thay đổi nhị phân không tương thích với thư viện mà không biên dịch lại mã máy khách. Đặc tả ngôn ngữ Java §13 chi tiết tất cả các thay đổi như vậy, nổi bật nhất, thay đổi các static
trường / phương thức không riêng tư thành static
hoặc ngược lại.
Biên dịch lại mã máy khách đối với thư viện mới và bạn nên truy cập.
CẬP NHẬT: Nếu bạn xuất bản một thư viện công cộng, bạn nên tránh thực hiện các thay đổi nhị phân không tương thích càng nhiều càng tốt để duy trì cái gọi là "tương thích ngược nhị phân". Cập nhật các lọ phụ thuộc một mình lý tưởng không nên phá vỡ ứng dụng hoặc bản dựng. Nếu bạn phải phá vỡ tính tương thích ngược nhị phân, bạn nên tăng số phiên bản chính (ví dụ: từ 1.xy lên 2.0.0) trước khi phát hành thay đổi.
*.class
tệp) và biên dịch lại chưa? Chỉnh sửa tập tin có hiệu lực tương tự.
Thư viện mới được đóng gói của bạn không tương thích nhị phân ngược (BC) với phiên bản cũ. Vì lý do này, một số ứng dụng khách thư viện không được biên dịch lại có thể đưa ra ngoại lệ.
Đây là danh sách đầy đủ các thay đổi trong API thư viện Java có thể khiến các máy khách được xây dựng với phiên bản cũ của thư viện ném java.lang. Không tương thíchClassChangeError nếu chúng chạy trên một cái mới (tức là phá BC):
Lưu ý : Có nhiều trường hợp ngoại lệ khác gây ra bởi các thay đổi không tương thích khác: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , ConfirmError , NoClassDefFoundError và AbstractMethodError .
Bài viết hay hơn về BC là "Phát triển API dựa trên Java 2: Đạt được tính tương thích nhị phân API" được viết bởi Jim des Rivières.
Ngoài ra còn có một số công cụ tự động để phát hiện những thay đổi như vậy:
Sử dụng trình kiểm tra tuân thủ japi cho thư viện của bạn:
japi-compliance-checker OLD.jar NEW.jar
Sử dụng công cụ clirr:
java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar
Chúc may mắn!
Trong khi những câu trả lời này đều đúng, việc giải quyết vấn đề thường khó khăn hơn. Nhìn chung, đó là kết quả của hai phiên bản khác nhau nhẹ của cùng một phụ thuộc vào đường dẫn lớp và hầu như luôn được gây ra bởi một siêu lớp khác so với ban đầu được tạo ra so với trên đường dẫn lớp hoặc một số lần nhập của đóng cửa bắc cầu là khác nhau, nhưng nói chung là ở lớp khởi tạo và gọi hàm tạo. (Sau khi tải lớp thành công và gọi ctor, bạn sẽ nhận được NoSuchMethodException
hoặc không có gì.)
Nếu hành vi xuất hiện ngẫu nhiên, có khả năng đó là kết quả của một lớp chương trình đa luồng tải các phụ thuộc bắc cầu khác nhau dựa trên mã nào bị tấn công trước.
Để giải quyết những điều này, hãy thử khởi chạy VM với -verbose
tư cách là một đối số, sau đó xem xét các lớp đang được tải khi ngoại lệ xảy ra. Bạn sẽ thấy một số thông tin đáng ngạc nhiên. Chẳng hạn, có nhiều bản sao của cùng một phụ thuộc và các phiên bản mà bạn không bao giờ mong đợi hoặc sẽ chấp nhận nếu bạn biết chúng được đưa vào.
Giải quyết các lọ trùng lặp với Maven được thực hiện tốt nhất với sự kết hợp giữa plugin-phụ thuộc maven và plugin -thực thi-maven trong Maven (hoặc Plugin Biểu đồ phụ thuộc của SBT , sau đó thêm các lọ đó vào một phần của POM cấp cao nhất của bạn hoặc dưới dạng phụ thuộc được nhập các yếu tố trong SBT (để loại bỏ các phụ thuộc đó).
Chúc may mắn!
Tôi cũng đã phát hiện ra rằng, khi sử dụng JNI, gọi một phương thức Java từ C ++, nếu bạn truyền tham số cho phương thức Java được gọi theo thứ tự sai, bạn sẽ gặp lỗi này khi bạn cố sử dụng các tham số bên trong phương thức được gọi (vì chúng sẽ không phải là loại đúng). Ban đầu tôi rất ngạc nhiên rằng JNI không thực hiện việc kiểm tra này cho bạn như là một phần của kiểm tra chữ ký lớp khi bạn gọi phương thức, nhưng tôi cho rằng họ không thực hiện loại kiểm tra này vì bạn có thể truyền các tham số đa hình và họ phải giả sử bạn biết những gì bạn đang làm.
Ví dụ Mã JNI C ++:
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.
}
Mã Java ví dụ:
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...
}
}
}
Tôi đã có cùng một vấn đề và sau đó tôi phát hiện ra rằng tôi đang chạy ứng dụng trên phiên bản Java 1.4 trong khi ứng dụng được biên dịch trên phiên bản 6.
Trên thực tế, lý do là có một thư viện trùng lặp, một thư viện nằm trong đường dẫn lớp và một thư viện khác được bao gồm trong một tệp jar nằm trong đường dẫn lớp.
Một tình huống khác mà lỗi này có thể xuất hiện là với Bảo hiểm Mã Emma.
Điều này xảy ra khi gán Object cho giao diện. Tôi đoán điều này có liên quan đến việc Object được kết nối và không tương thích nhị phân nữa.
http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351
May mắn là sự cố này không xảy ra với Cobertura, vì vậy tôi đã thêm cobertura-maven-plugin trong các plugin báo cáo về pom.xml của mình
Tôi đã phải đối mặt với vấn đề này trong khi không triển khai và triển khai lại một cuộc chiến với cá thủy tinh. Cấu trúc lớp học của tôi là như thế này,
public interface A{
}
public class AImpl implements A{
}
và nó đã được đổi thành
public abstract class A{
}
public class AImpl extends A{
}
Sau khi dừng và khởi động lại tên miền, nó hoạt động tốt. Tôi đã sử dụng cá thủy tinh 3.1.43
Tôi có một ứng dụng web triển khai hoàn toàn tốt trên tomcat của máy cục bộ của tôi (8.0.20). Tuy nhiên, khi tôi đưa nó vào môi trường qa (tomcat - 8.0.20), nó vẫn tiếp tục đưa cho tôi IncompiverseClassChangeError và tôi đã phàn nàn rằng tôi đang mở rộng trên một giao diện. Giao diện này đã được thay đổi thành một lớp trừu tượng. Và tôi đã biên soạn các lớp cha mẹ và con và tôi vẫn tiếp tục gặp vấn đề tương tự. Cuối cùng, tôi muốn gỡ lỗi, vì vậy, tôi đã thay đổi phiên bản trên cha thành x.0.1-SNAPSHOT và sau đó biên dịch mọi thứ và bây giờ nó đang hoạt động. Nếu ai đó vẫn gặp phải vấn đề sau khi làm theo các câu trả lời được đưa ra ở đây, vui lòng đảm bảo rằng các phiên bản trong pom.xml của bạn cũng đúng. Thay đổi các phiên bản để xem nếu nó hoạt động. Nếu vậy, sau đó sửa vấn đề phiên bản.
Câu trả lời của tôi, tôi tin rằng, sẽ là Intellij cụ thể.
Tôi đã xây dựng lại sạch sẽ, thậm chí còn đi xa đến mức xóa thủ công các thư mục "ra" và "mục tiêu". Intellij có "bộ nhớ cache và khởi động lại không hợp lệ", đôi khi xóa các lỗi lẻ. Lần này nó không hoạt động. Tất cả các phiên bản phụ thuộc đều trông chính xác trong menu cài đặt dự án-> mô-đun.
Câu trả lời cuối cùng là xóa thủ công vấn đề của tôi khỏi repo maven cục bộ của tôi. Một phiên bản cũ của bouncycastle là thủ phạm (tôi biết tôi vừa thay đổi phiên bản và đó sẽ là vấn đề) và mặc dù phiên bản cũ không xuất hiện ở đâu trong những gì đang được xây dựng, nó đã giải quyết vấn đề của tôi. Tôi đã sử dụng intellij phiên bản 14 và sau đó nâng cấp lên 15 trong quá trình này.
Trong trường hợp của tôi, tôi gặp phải lỗi này theo cách này. pom.xml
dự án của tôi xác định hai phụ thuộc A
và B
. Và cả hai A
và B
được xác định phụ thuộc vào cùng một tạo phẩm (gọi nó C
) nhưng các phiên bản khác nhau của nó ( C.1
và C.2
). Khi điều này xảy ra, đối với mỗi lớp trong C
maven chỉ có thể chọn một phiên bản của lớp từ hai phiên bản (trong khi xây dựng một uber-jar ). Nó sẽ chọn phiên bản "gần nhất" dựa trên các quy tắc hòa giải phụ thuộc của nó và sẽ đưa ra cảnh báo "Chúng tôi có một lớp trùng lặp ..." Nếu một chữ ký phương thức / lớp thay đổi giữa các phiên bản, nó có thể gây ra java.lang.IncompatibleClassChangeError
ngoại lệ nếu phiên bản không chính xác là được sử dụng trong thời gian chạy.
Nâng cao: Nếu A
phải sử dụng v1 C
và B
phải sử dụng v2 C
, thì chúng ta phải di chuyển C
vào A
và B
các poms để tránh xung đột lớp (chúng ta có cảnh báo lớp trùng lặp) khi xây dựng dự án cuối cùng phụ thuộc vào cả hai A
và B
.
Trong trường hợp của tôi, lỗi xuất hiện khi tôi thêm com.nimbusds
thư viện trong ứng dụng của mình được triển khai Websphere 8.5
.
Ngoại lệ dưới đây đã xảy ra:
Nguyên nhân bởi: java.lang.IncompiverseClassChangeError: org.objectweb.asm.AnnotationVisitor
Giải pháp là loại trừ bình asm ra khỏi thư viện:
<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>
Nếu đây là bản ghi các sự cố có thể xảy ra của lỗi này thì:
Tôi vừa gặp lỗi này trên WAS (8.5.0.1), trong quá trình tải CXF (2.6.0) của cấu hình lò xo (3.1.1_release) trong đó BeanInstantiationException đã cuộn lên Phần mở rộng CXF, cuộn lên một IncompizableClassChangeError. Đoạn mã sau đây cho thấy ý chính của dấu vết ngăn xếp:
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
Trong trường hợp này, giải pháp là thay đổi thứ tự đường dẫn của mô-đun trong tệp chiến tranh của tôi. Đó là, mở ứng dụng chiến tranh trong bảng điều khiển WAS bên dưới và chọn (các) mô đun máy khách. Trong cấu hình mô-đun, đặt tải lớp là "cha mẹ cuối cùng".
Điều này được tìm thấy trong bảng điều khiển WAS:
Tài liệu cho một kịch bản khác sau khi đốt quá nhiều thời gian.
Hãy chắc chắn rằng bạn không có một lọ phụ thuộc có một lớp có chú thích EJB trên đó.
Chúng tôi đã có một tệp jar chung có @local
chú thích. Lớp đó sau đó đã được chuyển ra khỏi dự án chung đó và vào dự án jar ejb chính của chúng tôi. Bình ejb của chúng tôi và bình chung của chúng tôi đều được bó trong một tai. Phiên bản của phụ thuộc jar chung của chúng tôi đã không được cập nhật. Do đó, 2 lớp đang cố gắng trở thành một cái gì đó với những thay đổi không tương thích.
Vì một số lý do, ngoại lệ tương tự cũng được đưa ra khi sử dụng JNI và truyền đối số j class thay vì công việc khi gọi a Call*Method()
.
Điều này tương tự như câu trả lời từ 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;
}
Tôi biết rằng hơi muộn để trả lời câu hỏi này 5 năm sau khi được hỏi nhưng đây là một trong những lượt truy cập hàng đầu khi tìm kiếm java.lang.IncompatibleClassChangeError
vì vậy tôi muốn ghi lại trường hợp đặc biệt này.
Thêm 2 xu của tôi. Nếu bạn đang sử dụng scala và sbt và đăng nhập scala làm phụ thuộc, thì điều này có thể xảy ra vì phiên bản trước của scala-log có tên scala-logging-api.So; về cơ bản, độ phân giải phụ thuộc không xảy ra do khác nhau tên dẫn đến lỗi thời gian chạy trong khi khởi chạy ứng dụng scala.
Một nguyên nhân bổ sung của vấn đề này là nếu bạn đã Instant Run
kích hoạt cho Android Studio.
Nếu bạn thấy bạn bắt đầu nhận được lỗi này, hãy tắt Instant Run
.
Instant Run
sửa đổi một số lượng lớn các thứ trong quá trình phát triển, để giúp cung cấp các bản cập nhật cho Ứng dụng đang chạy của bạn nhanh hơn. Do đó chạy ngay lập tức. Khi nó hoạt động, nó thực sự hữu ích. Tuy nhiên, khi một sự cố như đình công này, điều tốt nhất cần làm là tắt Instant Run
cho đến khi phiên bản Android Studio tiếp theo phát hành.