2013-04-18 1 views
59

Это вопрос интервью.Что еще может вызывать исключение ClassCastException в java?

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

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

Сценарий:

  • положить объект класса С1, чтобы кэш с ключом "на"

Позже код:

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

Этот код бросает исключение ClassCastException.

В чем могут быть причины?

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

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

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

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

+26

Это больной/сложный вопрос для интервью – Eugene

+3

Возможно, реализация 'get' - это' throw new ClassCastException(); '..? –

+1

oh забыл это спросил его, и он сказал, что его местный листинг – tgkprog

ответ

52

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

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

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

Это обычно происходит, когда в каждый модуль считывания и вставки входит одна и та же банка, содержащая C1.
Поскольку большинство контейнеров сначала используют родительский загрузчик классов, а затем локальный загрузчик классов (Parent first), общее решение проблемы заключается в том, чтобы вместо этого загрузить класс в ближайший общий родительский модуль для модулей вставки и чтения.
Если вы переместите модуль, содержащий класс C1, в родительский модуль, вы заставляете оба подмодуля получать класс от родителя, удаляя различия между классами.

+1

, некоторый код, доказывающий это, скорее всего, достигнет золотой партии. +1 – Eugene

+2

Этот точный сценарий укусил меня в Tomcat. Tomcat использует иерархические загрузчики классов, и если у вас есть зависимости классов, которые пересекают иерархическую границу, вы по существу НЕ МОЖЕТ бросать, если вы не переместите все классы на высокий общий уровень. –

+1

. Ух, я собирался сказать, что там было 2 разных веб-приложения в одной JVM, но затем лучше подумать не о том, почему это приведет к классу. Обучение: точно так же, как в коде и в Db, хорошо иметь функцию в одном месте, кажется, что то же самое верно и в других местах (общий lib для всех веб-приложений/добавление основного пути к контейнеру контейнера, если вы можете или 1 приложение на контейнер). – tgkprog

31

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

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

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

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

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

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

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

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

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

Другим способом избежать этого является работа только с интерфейсами, которые являются общими объектами. Затем интерфейсы должны быть загружены выше в иерархии загрузчиков классов, но сами классы этого не делают. Ваш пример получения объекта из кеша будет таким же, но класс 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, а также.

+0

спасибо, что это сработало на моем местном. изменил src, так что имя пакета отличается в a-src (как и ожидалось, все равно было то же самое). будет делать это в 2 веб-приложениях с внешней memcache когда-нибудь :) @ ed-plese – tgkprog

0

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

+4

Хорошо повышающий рейтинг не вызывает никаких исключений в Java.! –

3

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

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

+0

интересно, хотя не уверен, как это помогает здесь? – tgkprog

+0

сообщение в блоге подводит итог «Практическое применение этого блога: давайте посмотрим правде в глаза». – tgkprog

+1

@tgkprog - Вопрос в том, как извлеченный объект мог быть неправильного класса - ясно, если ** ключ ** изменился, тогда все ставки отключены. Вы можете положить X с ключом «a», затем изменить «a» и поместить Y с помощью клавиши «a». Последующий получает с ключом «a», затем получит Y. – OldCurmudgeon