60

これはインタビューの質問です。

インタビューは終わりましたが、この質問はまだ頭の中にあります。

採用されなかったので、面接官に質問することはできません。

シナリオ:

  • クラス C1 のオブジェクトをキー「a」でキャッシュに入れる

後のコード:

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

このコードは ClassCastException をスローします。

理由は何ですか?

私が言ったのは、他の誰かが同じキーを持つ別のオブジェクトを置き、それを上書きしたからです。いいえ、他の可能性を考えてくださいと言われました。

私は、クラス C1 を定義する jar がこのノードで使用できない可能性があると言いました (これがクラス キャストまたは ClassNotFoundException になるかどうかはわかりませんが、現在何らかの手がかりをつかんでいました。それから、クラスのバージョンが間違っている可能性があると言いましたか?彼らは言いました。クラス C1 の同じ jar がすべてのノードにあります)。

編集/追加get が ClassCast をスローしているかどうかを尋ねられましたが、いいえと言われました。その後、そのような問題を解決するための私のアクションは、アクションを模倣し、例外の後により良いログ (スタック トレース) を配置するテスト jsp をドロップすることであると彼に言いました。それが質問の 2 番目の部分でした (これが本番環境で発生した場合、なぜ、そして何をしますか)

キャッシュを取得するとキャストの問題が発生する理由について、他の誰かが考えを持っていますか?

4

4 に答える 4

32

これClassCastExceptionは、同じクラスが複数の異なるクラスローダーによってロードされ、クラスのインスタンスがそれらの間で共有されている場合に発生する可能性があります。

次の階層の例を考えてみましょう。

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

一般に、次のことは正しいと思いますが、これから逸脱するカスタムクラスローダーを作成できます。

  • SystemClassloader によってロードされたクラスのインスタンスは、どのクラスローダー コンテキストでもアクセスできます。
  • AppClassloader によってロードされたクラスのインスタンスは、どのクラスローダー コンテキストでもアクセスできます。
  • Classloader1 によってロードされたクラスのインスタンスは、Classloader2 からアクセスできません。
  • Classloader2 によってロードされたクラスのインスタンスは、Classloader1 からアクセスできません。

前述のように、これが発生する一般的なシナリオは、一般的に言えば AppClassloader が appserver で構成されたクラスパスに非常に似ている Web アプリのデプロイであり、Classloader1 と Classloader2 は個別にデプロイされた Web アプリのクラスパスを表します。

複数の Web アプリが同じ JAR/クラスをデプロイするClassCastException場合、Web アプリがキャッシュや共有セッションなどのオブジェクトを共有するメカニズムがある場合に発生する可能性があります。

これが発生する可能性がある別の同様のシナリオは、クラスが Web アプリによって読み込まれ、これらのクラスのインスタンスがユーザー セッションまたはキャッシュに格納されている場合です。Web アプリが再デプロイされると、これらのクラスは新しいクラスローダーによって再ロードされ、セッションまたはキャッシュからオブジェクトにアクセスしようとすると、この例外がスローされます。

本番環境でこの問題を回避する方法の 1 つは、JAR をクラスローダー階層の上位に移動することです。そのため、各 Web アプリに同じ JAR を含める代わりに、これらをアプリサーバーのクラスパスに含める方がうまくいく場合があります。これにより、クラスは 1 回だけ読み込まれ、すべての Web アプリからアクセスできるようになります。

これを回避するもう 1 つの方法は、オブジェクトを共有しているインターフェイスでのみ操作することです。次に、インターフェースをクラスローダー階層の上位にロードする必要がありますが、クラス自体はロードしません。キャッシュからオブジェクトを取得する例は同じですが、C1クラスは実装するインターフェースに置き換えられますC1

以下は、このシナリオを再現するために個別に実行できるサンプル コードです。これは最も簡潔ではなく、確かにそれを説明するためのより良い方法があるかもしれませんが、上記の理由で例外をスローします.

パッケージにはa.jar、次の 2 つのクラスAMyRunnable. これらは、2 つの独立したクラスローダーによって複数回ロードされます。

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());
    }
}

上記のクラスとは別に、次のプログラムを実行します。上記のクラスが JAR ファイルから確実にロードされるように、クラスパスを上記のクラスと共有してはなりません。

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);
    }
}

これを実行すると、次の出力が表示されます。

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)

ソースもGitHubに上げておきます。

于 2013-04-21T04:10:57.103 に答える
3

String intern最後に、何者かが string のテーブルをハッキングしました"a"

それがどのように行われるかの例をここで見てください。

于 2013-04-21T20:21:28.613 に答える
0

おそらく、C1 は抽象クラスであり、get 関数も返される前に C1 にキャストされた (もちろん C1 のサブクラスの) オブジェクトを返すためでしょうか?

于 2013-04-21T05:01:20.100 に答える