UFO ET IT

Java에서 ClassCastException을 던질 수있는 다른 것은 무엇입니까?

ufoet 2020. 12. 8. 20:29
반응형

Java에서 ClassCastException을 던질 수있는 다른 것은 무엇입니까?


인터뷰 질문입니다.

인터뷰는 끝났지 만이 질문은 여전히 ​​마음에 있습니다.

직업을 얻지 못해서 면접관에게 물어볼 수 없습니다.

대본:

  • "a"키를 사용하여 클래스 C1의 객체를 캐시에 넣습니다.

이후 코드 :

C1 c1FromCache = (C1) cache.get("a");

이 코드는 ClassCastException을 발생시킵니다.

그 이유는 무엇일까요?

다른 사람 이 같은 키를 가진 다른 물건을 넣어서 덮어 썼기 때문에 나는 말했다 . 다른 가능성을 생각해보십시오.

클래스 C1을 정의하는 jar가이 노드에서 사용 가능하지 않을 수 있다고 말했습니다 (이로 인해 클래스 캐스트 또는 ClassNotFoundException이 발생하는지 확실하지 않지만 지금 어떤 리드를 파악하고있었습니다. 그런 다음 잘못된 버전의 클래스라고 말했습니까? 클래스 C1의 동일한 항아리가 모든 노드에 있습니다).

편집 / 추가 get이 ClassCast를 던 졌는지 물었지만 아니오라고 들었습니다. 그 후 나는 그에게 그러한 문제를 해결하기위한 나의 행동은 행동을 모방하고 예외 후에 더 나은 로깅 (스택 추적)을 넣을 테스트 jsp를 드롭하는 것이라고 말했다. 그것이 질문의 두 번째 부분이었습니다 (이런 일이 프로덕션에서 일어난다면 왜 그리고 무엇을 할 것입니까)

다른 사람이 캐시 가져 오기 로 인해 캐스트 문제가 발생 하는 이유에 대한 아이디어가 있습니까?


한 가지 이유는 객체를 삽입하는 코드 부분이 객체를 검색하는 코드와 다른 클래스 로더를 사용하기 때문일 수 있습니다.
클래스의 인스턴스는 다른 클래스 로더에 의해로드 된 동일한 클래스로 캐스트 될 수 없습니다.

편집에 대한 응답 :

프로덕션에서 이런 일이 발생하면 어떻게 하시겠습니까?

이것은 일반적으로 읽기 및 삽입 모듈에 C1.
대부분의 컨테이너는 부모 클래스 로더를 먼저 시도한 다음 로컬 클래스 로더 ( 상위 우선 전략)를 시도하므로 문제에 대한 일반적인 해결책은 삽입 및 읽기 모듈에 가장 가까운 공통 부모에있는 클래스를 대신로드하는 것입니다. 클래스가
포함 된 모듈을 C1부모 모듈로 이동하면 두 하위 모듈 모두 부모에서 클래스를 가져 와서 클래스 로더 차이를 제거합니다.


ClassCastException같은 클래스가 여러 다른 클래스 로더와 그들 사이에 공유되고있는 클래스의 인스턴스에 의해로드 된 경우 발생할 수 있습니다.

다음 예제 계층을 고려하십시오.

SystemClassloader <--- AppClassloader <--+--- Classloader1
                                         |
                                         +--- Classloader2

나는 일반적으로 다음이 사실이라고 생각하지만 이것에서 벗어나는 커스텀 클래스 로더를 작성할 수 있습니다.

  • SystemClassloader에 의해로드 된 클래스의 인스턴스는 모든 클래스 로더 컨텍스트에서 액세스 할 수 있습니다.
  • AppClassloader에 의해로드 된 클래스의 인스턴스는 모든 클래스 로더 컨텍스트에서 액세스 할 수 있습니다.
  • Classloader1에서로드 한 클래스의 인스턴스는 Classloader2에서 액세스 할 수 없습니다.
  • Classloader2에서로드 한 클래스의 인스턴스는 Classloader1에서 액세스 할 수 없습니다.

언급했듯이 이러한 상황이 발생하는 일반적인 시나리오는 일반적으로 AppClassloader가 appserver에 구성된 클래스 경로와 매우 유사하고 Classloader1 및 Classloader2가 개별적으로 배포 된 웹앱의 클래스 경로를 나타내는 웹 앱 배포입니다.

여러 웹 앱이 동일한 JAR / 클래스를 배포하는 ClassCastException경우 웹 앱이 캐시 또는 공유 세션과 같은 개체를 공유하는 메커니즘이 있으면 이러한 문제 가 발생할 수 있습니다.

이것이 발생할 수있는 또 다른 유사한 시나리오는 클래스가 웹 앱에 의해로드되고 이러한 클래스의 인스턴스가 사용자 세션 또는 캐시에 저장되는 경우입니다. 웹앱이 재배포 된 경우 이러한 클래스는 새 클래스 로더에 의해 다시로드되고 세션 또는 캐시에서 개체에 액세스하려고하면이 예외가 발생합니다.

프로덕션에서이 문제를 방지하는 한 가지 방법은 클래스 로더 계층에서 JAR을 더 높은 위치로 이동하는 것입니다. 따라서 각 웹 앱에 동일한 JAR을 포함하는 대신 appserver의 클래스 경로에 포함하는 것이 더 효과적 일 수 있습니다. 이렇게하면 클래스가 한 번만로드되고 모든 웹 앱에서 액세스 할 수 있습니다.

이를 방지하는 또 다른 방법은 객체를 공유하는 인터페이스에서만 작동하는 것입니다. 그런 다음 인터페이스는 클래스 로더 계층 구조에서 상위에로드되어야하지만 클래스 자체는 그렇지 않습니다. 캐시에서 객체를 가져 오는 예제는 동일하지만 C1클래스는 C1구현 하는 인터페이스로 대체됩니다 .

다음은이 시나리오를 재현하기 위해 독립적으로 실행할 수있는 몇 가지 샘플 코드입니다. 가장 간결한 것은 아니며 확실히 설명하는 더 좋은 방법이있을 수 있지만 위에서 언급 한 이유로 예외가 발생합니다.

에서 a.jar패키지 두 클래스를 따라 AMyRunnable. 이들은 두 개의 독립적 인 클래스 로더에 의해 여러 번로드됩니다.

package classloadertest;

public class A {
    private String value;

    public A(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "<A value=\"" + value + "\">";
    }
}

package classloadertest;

import java.util.concurrent.ConcurrentHashMap;

public class MyRunnable implements Runnable {
    private ConcurrentHashMap<String, Object> cache;
    private String name;

    public MyRunnable(String name, ConcurrentHashMap<String, Object> cache) {
        this.name = name;
        this.cache = cache;
    }

    @Override
    public void run() {
        System.out.println("Run " + name + ": running");

        // Set the object in the cache
        A a = new A(name);
        cache.putIfAbsent("key", a);

        // Read the object from the cache which may be differed from above if it had already been set.
        A cached = (A) cache.get("key");
        System.out.println("Run " + name + ": cache[\"key\"] = " + cached.toString());
    }
}

Independent of the classes above run the following program. It must not share a classpath with the above classes to ensure that they are loaded from the JAR file.

package classloadertest;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void run(String name, ConcurrentHashMap<String, Object> cache) throws Exception {
        // Create a classloader using a.jar as the classpath.
        URLClassLoader classloader = URLClassLoader.newInstance(new URL[] { new File("a.jar").toURI().toURL() });

        // Instantiate MyRunnable from within a.jar and call its run() method.
        Class<?> c = classloader.loadClass("classloadertest.MyRunnable");
        Runnable r = (Runnable)c.getConstructor(String.class, ConcurrentHashMap.class).newInstance(name, cache);
        r.run();
    }

    public static void main(String[] args) throws Exception {
        // Create a shared cache.
        ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<String, Object>();

        run("1", cache);
        run("2", cache);
    }
}

On running this the following output is displayed:

Run 1: running
Run 1: cache["key"] = <A value="1">
Run 2: running
Exception in thread "main" java.lang.ClassCastException: classloadertest.A cannot be cast to classloadertest.A
        at classloadertest.MyRunnable.run(MyRunnable.java:23)
        at classloadertest.Main.run(Main.java:16)
        at classloadertest.Main.main(Main.java:24)

I put the source up on GitHub as well.


And finally, someone hacked the String intern table for the string "a".

See an example of how it can be done here.


Well maybe because C1 is an abstract class, and the get function also returns on object(of a subclass of C1 of course) which was casted to C1 before returning?

참고URL : https://stackoverflow.com/questions/16092141/what-else-can-throw-a-classcastexception-in-java

반응형