Что еще может бросить ClassCastException в java?


Это вопрос для интервью.

интервью закончилось, но этот вопрос все еще находится на моем уме.

Я не могу спросить интервьюера, так как я не получил работу.

сценарий:

  • поместите объект класса C1 в кэш с ключом "a"

последующий код:

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

этот код вызывает ClassCastException.

что может причины быть?

Я сказал, потому что кто-то другой поставить другой объект с тем же ключом и так он перезаписывался. Мне сказали нет, подумайте о других возможностях.

Я сказал, что, возможно, jar, определяющий класс C1, не был доступен на этом узле (не уверен, приведет ли это к классу cast или ClassNotFoundException, но теперь я хватался за любой вывод. Тогда я сказал, Может быть, неправильный вариант класса? Они сказали, что одна и та же банка класса C1 есть во всех узлы.)

Изменить/ Добавить спросил, Если get бросал ClassCast, но ему сказали нет. после этого я сказал ему, что мое действие для решения такой проблемы будет заключаться в том, чтобы сбросить тестовый jsp, который будет имитировать действия и лучше регистрировать (трассировка стека) после исключения. это была 2-я часть вопроса (почему и что бы вы сделали, если бы это произошло в производстве)

есть ли у кого-нибудь еще идеи о том, почему кэш get приведет к броску проблема?

4 59

4 ответа:

одной из причин может быть то, что часть кода вставить объект использует другой загрузчик классов, чем код, чтобы получить это.
Экземпляр класса не может быть приведен к одному классу загружен другим загрузчиком.

ответ на редактирование:

что бы вы сделали, если бы это произошло в производстве?

обычно это происходит, когда модули чтения и вставки включают в себя одну и ту же банку, содержащую C1.
Поскольку большинство контейнеров сначала пробуют Родительский загрузчик классов, а затем локальный загрузчик классов (родитель "первый"!--16--> strategy), общее решение проблемы заключается в том, чтобы вместо этого загрузить класс в ближайший общий родитель для модулей вставки и чтения.
Если вы переместите модуль, содержащий C1 класс для родительского модуля, вы заставляете оба подмодуля, чтобы получить класс от родителя, удаляя любые различия загрузчика классов.

The ClassCastException может произойти, если один и тот же класс был загружен несколькими различными загрузчиками классов и экземпляры классов совместно используются между ними.

рассмотрим следующий пример иерархии.

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

я думаю, что в целом следующее верно, но пользовательские загрузчики классов могут быть написаны, которые отклоняются от этого.

  • экземпляры классов, загруженных SystemClassloader, доступны в любом из загрузчиков классов контексты.
  • экземпляры классов, загруженных AppClassloader, доступны в любом из контекстов classloader.
  • экземпляры классов, загруженных Classloader1, недоступны Classloader2.
  • экземпляры классов, загруженных Classloader2, недоступны Classloader1.

как уже упоминалось, общий сценарий, в котором это происходит, - это развертывание веб-приложений, где, как правило, AppClassloader очень похож на classpath настроенные в сервере приложений, а затем Classloader1 и Classloader2 представляют пути к классам индивидуально развернутых веб-приложений.

если несколько веб-приложений развертывают одни и те же банки/классы, то ClassCastException может произойти, если существует какой-либо механизм для веб-приложений для совместного использования объектов, таких как кэш или общий сеанс.

другой подобный сценарий, где это может произойти, если классы загружаются веб-приложением и экземпляры этих классов хранятся в сеансе пользователя или кэш. Если веб-приложение повторно развертывается, то эти классы перезагружаются новым загрузчиком классов и попытка доступа к объектам из сеанса или кэша вызовет это исключение.

один из способов избежать этой проблемы в производстве-переместить банки выше в иерархии загрузчика классов. Поэтому вместо того, чтобы включать одну и ту же банку в каждое веб-приложение, лучше включить их в путь к классам сервера приложений. При этом классы загружаются только один раз и доступны для всех веб-приложений.

другой способ избежать этого заключается в работе только на интерфейсах, что общие объекты. Затем интерфейсы должны быть загружены выше в иерархии загрузчиков классов, а сами классы не делают. Ваш пример получения объекта из кэша будет таким же, но C1 класс будет заменен интерфейс, что C1 реализует.

ниже приведен пример кода, который может выполняться самостоятельно воссоздать этот сценарий. Это не самый краткий и, конечно, могут быть лучшие способы проиллюстрировать его, но он бросает исключение по причинам, упомянутым выше.

на a.jar упакуйте следующие два класса,A и MyRunnable. Они загружаются несколько раз двумя независимыми загрузчиками классов.

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 как хорошо.

и, наконец, кто-то взломал Stringintern таблица для строки "a".

Смотрите пример того, как это можно сделать здесь.

Ну, может быть, потому что C1 является абстрактным классом, и функция get также возвращает объект(конечно, подкласса C1), который был приведен к C1 перед возвращением?