[java] java.lang.IncompatibleClassChangeError의 원인은 무엇입니까?

Java 라이브러리를 JAR로 패키징하고 있으며 java.lang.IncompatibleClassChangeError메소드를 호출하려고 할 때 많은 것을 던지고 있습니다. 이 오류는 무작위로 나타납니다. 이 오류의 원인은 무엇입니까?



답변

이것은 클라이언트 코드를 다시 컴파일하지 않고 라이브러리에 대해 호환되지 않는 바이너리 변경을 수행했음을 의미합니다. Java Language Specification §13 은 이러한 모든 변경 사항을 가장 두드러지게 설명하며, 비 static개인 필드 / 방법을 변경 static하거나 그 반대로 변경합니다.

새 라이브러리에 대해 클라이언트 코드를 다시 컴파일하십시오.

업데이트 : 공용 라이브러리를 게시하는 경우 “이진 역 호환성”이라는 것을 유지하기 위해 호환되지 않는 이진 변경을 최대한 피해야합니다. 의존성 jar 만 업데이트하는 것이 이상적으로 응용 프로그램이나 빌드를 중단해서는 안됩니다. 이진 역 호환성을 중단 해야하는 경우 변경 사항을 릴리스하기 전에 주 버전 번호 (예 : 1.xy에서 2.0.0으로)를 늘리는 것이 좋습니다 .


답변

새로 패키지 된 라이브러리 이전 버전의 BC ( Backward Binary Compatible)가 아닙니다 . 이러한 이유로 재 컴파일되지 않은 라이브러리 클라이언트 중 일부는 예외를 발생시킬 수 있습니다.

이전 버전의 라이브러리로 빌드 한 클라이언트가 java.lang을 발생시킬 수있는 Java 라이브러리 API 의 전체 변경 사항 목록입니다. 그들이 새로운 것으로 실행될 때 (즉, BC를 깨는 경우) IncompatibleClassChangeError :

  1. 최종 필드가 아닌 정적 필드
  2. 일정하지 않은 필드는 고정되지 않습니다.
  3. 클래스가 인터페이스가되어
  4. 인터페이스가 클래스가되어
  5. 클래스 / 인터페이스에 새 필드를 추가하거나 새 수퍼 클래스 / 수퍼 인터페이스를 추가하면 클라이언트 클래스 C의 수퍼 인터페이스에서 정적 필드가 추가 된 필드 (같은 이름으로)를 C의 슈퍼 클래스 (매우 드문 경우).

참고 : 다른 호환되지 않는 변경으로 인해 NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundErrorAbstractMethodError 등의 다른 많은 예외 가 있습니다 .

BC에 대한 더 좋은 논문은 Jim des Rivières가 작성한 “Evaving Java-based APIs 2 : Achieving API Binary Compatibility” 입니다.

이러한 변경 사항을 감지 하는 자동 도구 도 있습니다 .

라이브러리에 대한 japi 준수 검사기 사용법 :

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 (또는 SBT의 Dependency Graph Plugin ) 에서 maven-dependency-pluginmaven-enforcer-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...
        }
    }
}


답변

나는 같은 문제가 있었고 나중에 응용 프로그램이 버전 6에서 컴파일되는 동안 Java 버전 1.4에서 응용 프로그램을 실행하고 있음을 알았습니다.

실제로, 그 이유는 중복 라이브러리가 있었기 때문입니다. 하나는 클래스 경로 내에 있고 다른 하나는 클래스 경로 내에있는 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을 사용하고 있었다