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, а также.
Это больной/сложный вопрос для интервью – Eugene
Возможно, реализация 'get' - это' throw new ClassCastException(); '..? –
oh забыл это спросил его, и он сказал, что его местный листинг – tgkprog