java caching classcastexception

¿Qué otra cosa puede lanzar una ClassCastException en Java?



caching (4)

Bueno, tal vez porque C1 es una clase abstracta, y la función get también devuelve en el objeto (de una subclase de C1, por supuesto) que se fundió a C1 antes de regresar?

Esta es una pregunta de entrevista.

La entrevista terminó, pero esta pregunta todavía está en mi mente.

No puedo preguntarle al entrevistador, ya que no conseguí el trabajo.

Guión:

  • poner el objeto de clase C1 en un caché con la clave "a"

Código posterior:

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

Este código arroja una ClassCastException.

¿Cuáles pueden ser las razones?

Lo dije porque alguien más colocó otro objeto con la misma clave y lo sobrescribió. Me dijeron que no, piensa en otras posibilidades.

Dije que tal vez el jar que definía la clase C1 no estaba disponible en este nodo (no estoy seguro de si esto daría como resultado un elenco de clase o una ClassNotFoundException, pero estaba buscando alguna pista ahora. Entonces dije tal vez la versión incorrecta de la clase. Dijeron el mismo jarro de clase C1 está en todos los nodos).

Editar / Agregar Se le preguntó si el get estaba lanzando el ClassCast, pero se le dijo que no. después de eso, le dije que mi acción para resolver un problema de este tipo consistiría en incluir un jsp de prueba que simulara las acciones y que registrase mejor (seguimiento de la pila) después de la excepción. esa fue la segunda parte de la pregunta (por qué y qué harías si esto sucediera en producción)

¿Alguien más tiene alguna idea sobre por qué un caché se convertiría en un problema de conversión?


Una razón podría ser que la parte del código que inserta el objeto utiliza un cargador de clases diferente al código que lo recupera.
Una instancia de una clase no se puede convertir a la misma clase cargada por un cargador de clases diferente.

Respuesta a la edición:

¿Qué harías si esto sucediera en producción?

Esto generalmente ocurre cuando los módulos de lectura e inserción incluyen el mismo recipiente que contiene C1 .
Como la mayoría de los contenedores prueban primero el cargador de clases principal y luego el cargador de clases local (la primera estrategia principal), la solución común al problema es cargar la clase en el elemento primario común más cercano a los módulos de inserción y lectura.
Si mueve el módulo que contiene la clase C1 al módulo principal, fuerza a ambos submódulos a obtener la clase del elemento primario, eliminando cualquier diferencia de cargador de clase.


Y finalmente, alguien hackeó la tabla de intern String para la cadena "a" .

Vea un ejemplo de cómo se puede hacer here .


ClassCastException puede ocurrir si varios cargadores de clase diferentes cargan la misma clase y las instancias de las clases se comparten entre ellos.

Considere la siguiente jerarquía de ejemplo.

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

Creo que, en general, lo siguiente es cierto, pero se pueden escribir clasificadores personalizados que se apartan de esto.

  • Las instancias de clases cargadas por SystemClassloader son accesibles en cualquiera de los contextos del cargador de clases.
  • Las instancias de clases cargadas por AppClassloader son accesibles en cualquiera de los contextos del cargador de clases.
  • Installer no puede acceder a las instancias de las clases cargadas por Classloader1.
  • Installer no puede acceder a las instancias de las clases cargadas por Classloader2.

Como se mencionó, un escenario común en el que esto ocurre es en las implementaciones de aplicaciones web donde, en términos generales, AppClassloader se asemeja mucho al classpath configurado en el servidor de aplicaciones y luego Classloader1 y Classloader2 representan los classpaths de las aplicaciones web implementadas individualmente.

Si varias aplicaciones web implementan los mismos JAR / clases, la ClassCastException puede ocurrir si existe algún mecanismo para que las aplicaciones web compartan objetos, como un caché o una sesión compartida.

Otro escenario similar donde esto puede ocurrir es si las clases son cargadas por la aplicación web y las instancias de estas clases se almacenan en la sesión del usuario o caché. Si la aplicación web se redistribuye, un nuevo cargador de clases vuelve a cargar estas clases e intenta acceder a los objetos desde la sesión o la caché arrojará esta excepción.

Un método para evitar este problema en Producción es mover los JAR más arriba en la jerarquía del cargador de clases. Por lo tanto, en lugar de incluir el mismo JAR en cada aplicación web, puede ser mejor incluirlos en el classpath del servidor de aplicaciones. Al hacer esto, las clases se cargan solo una vez y todas las aplicaciones web pueden acceder a ellas.

Otro método para evitar esto es operar solo en las interfaces que comparten los objetos. Las interfaces deben cargarse más arriba en la jerarquía del cargador de clases, pero las clases no lo hacen. Su ejemplo de obtener el objeto de la memoria caché sería el mismo, pero la clase C1 sería reemplazada por una interfaz que implementa C1 .

A continuación se muestra un ejemplo de código que se puede ejecutar de forma independiente para recrear este escenario. No es el más conciso y sin duda puede haber mejores formas de ilustrarlo, pero arroja la excepción por las razones mencionadas anteriormente.

En el paquete a.jar las siguientes dos clases, A y MyRunnable . Estos son cargados varias veces por dos cargadores de clases independientes.

package classloadertest; public class A { private String value; public A(String value) { this.value = value; } @Override public String toString() { return "<A value=/"" + value + "/">"; } }

Y

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

Independientemente de las clases anteriores, ejecute el siguiente programa. No debe compartir un classpath con las clases anteriores para garantizar que se carguen desde el archivo 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); } }

Al ejecutar esto, se muestra el siguiente resultado:

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)

Puse la fuente en GitHub también.