如果同一类由多个不同的类装入器装入,并且这些类的实例在它们之间共享,则会发生这种情况。ClassCastException
请考虑以下示例层次结构。
SystemClassloader <--- AppClassloader <--+--- Classloader1
|
+--- Classloader2
我认为一般来说,以下是正确的,但是可以编写与此不同的自定义类加载器。
- 系统类装入器装入的类的实例可以在任何类装入器上下文中访问。
- AppClassloader 加载的类的实例可以在任何类加载器上下文中访问。
- Classloader1 装入的类的实例不能由 Classloader2 访问。
- Classloader2 装入的类的实例不能由 Classloader1 访问。
如前所述,发生这种情况的常见情况是 Web 应用部署,一般来说,AppClassloader 与应用服务器中配置的类路径非常相似,然后 Classloader1 和 Classloader2 表示单独部署的 Web 应用的类路径。
如果多个 Web 应用程序部署相同的 JAR/类,则如果 Web 应用程序有任何机制来共享对象(如缓存或共享会话),则可能会发生这种情况。ClassCastException
另一种可能发生这种情况的类似情况是,如果类由 Web 应用加载,并且这些类的实例存储在用户会话或缓存中。如果重新部署 Web 应用,则这些类将由新的类装入器重新装入,并且尝试从会话或缓存访问对象将引发此异常。
在生产中避免此问题的一种方法是将 JAR 移动到类装入器层次结构中的较高位置。因此,与其在每个 Web 应用中包含相同的 JAR,不如将这些 JAR 包含在应用服务器的类路径中。通过执行此操作,类仅加载一次,并且所有 Web 应用程序都可以访问这些类。
避免这种情况的另一种方法是仅在共享对象的接口上运行。然后,需要将接口装入类装入器层次结构中的较高位置,但类本身不需要。从缓存中获取对象的示例是相同的,但类将被实现的接口替换。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上。