2010-04-07 11 views
17

¿Cómo puedo hacer esto:lanzado a través del cargador de clases?

class Foo { 
    public static Foo get() throws Exception { 
    ClassLoader cl = new URLClassLoader(new URL[]{"foo.jar"}, null); // Foo.class is in foo.jar 
    return (Foo)cl.loadClass("Foo").newInstance(); // fails on class cast 
    } 
} 

Lo que necesito es para la JVM para considerar la instancia de Foo cl como si se trata de una instancia de Foo del cargador de clases del código de ejecución.

he visto estos enfoques, ninguno de ellos bueno para mí (el ejemplo anterior es un ejemplo de juguete):

  1. carga la clase (o una interfaz independiente) por un cargador de clases que es un padre de tanto el código de llamada como el classloader creado
  2. Serializar y deserializar el objeto.

Respuesta

20

No es posible. La identidad de clase consiste en el nombre completo y el cargador de clases.

Lanzar un objeto a una clase con el mismo nombre cargado por diferentes cargadores de clases no es diferente de tratar de lanzar un String a Integer, porque esas clases realmente podrían ser completamente diferentes a pesar de tener el mismo nombre.

+1

lo tanto, si ni siquiera podemos emitir estos nuevos casos, ¿cómo podemos utilizar? Si todo lo que podemos hacer es 'Object obj = cl.loadClass (" Foo "). NewInstance();', ¿cómo llamamos a los métodos de la nueva instancia de Foo? – Pacerier

+1

@Pacerier: bueno, podrías usar el reflejo. Pero un caso más práctico es hacer que las clases amplíen las clases o implementen interfaces de un cargador de clases padre en la jerarquía de la delegación que también están disponibles para el resto del código. Pero en el código de ejemplo, no hay un cargador de clases principal (el segundo parámetro para el constructor es nulo) ... –

+0

¿El rendimiento de la segunda opción será más rápido que el uso de la reflexión? ¿O es cierto que internamente usa la reflexión de todos modos? – Pacerier

3

Quizás algo usando interfaces y java.lang.reflect.Proxy se adapte a sus preferencias. Usando un InvocationHandler que encuentra e invoca el método relevante en la clase objetivo. (Tenga en cuenta que cualquier seguridad de código móvil que tenga se disparará si hace esto).

7

Acabo de pasar los últimos dos días luchando con este problema exacto y finalmente solucioné el problema usando el reflejo de java:

// 'source' is from another classloader 
final Object source = events[0].getSource(); 

if (source.getClass().getName().equals("org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread")) { 

    // I cannot cast to 'org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread' 
    // so I invoke the method 'terminate()' manually 
    Method method = source.getClass().getMethod("terminate", new Class[] {}); 
    method.invoke(source, new Object[] {}); 
} 

Espero que esto ayude a alguien.

+1

¿Cómo podemos hacer esto sin reflexión? Si cada método individual de cada objeto cargado por nuestro cargador de clases necesita ser llamado a través de la reflexión, ¿no severamente empantanar todo el programa? – Pacerier

0

Este es un antiguo puesto donde llegué porque quería hacer casi lo mismo, pero una versión más simple de la misma ...

En realidad funciona si ambos Foo y la clase cargado (en mi caso desde un .cava classfile en otro paquete) extiende la misma clase, por ejemplo, AbstractTestClass.

piezas de código:

public AbstractTestClass load(String toLoad) { 
    try{ 
     Class test = Class.forName("testsFolder.testLoadable"); 
     Constructor ctorlist[] = test.getDeclaredConstructors(); 
     for(Constructor aConstructor : ctorlist){ 
      if(...){// find the good constructor 
       Object loadedTest = aConstructor.newInstance(new Object[]{/*params*/}); 
       return (AbstractTestClass) test; 
      } 
     } 
    }catch(...){} 
    return new defaultTestClass(); 
} 

este modo puede insertar la clase cargada en un ArrayList<AbstractTestClass>.

+0

carga 'prueba' desde el mismo cargador de clase. Además, incluso si especifica otro, tendrá que ser un elemento secundario del cargador de clases actual o no podría realizar el envío a AbstractTestClass – IttayD

+0

@IttayD, tiene razón, necesita extender la clase principal (Foo en la pregunta). ¿No era el objetivo de la pregunta: "considerar la instancia de Foo desde cl como si fuera una instancia de Foo"? – Aname

1

No es posible enviar contenido en diferentes classLoader.

está esta solución con Gson, ejemplo convertir objeto de YourObject (Object es una clase YourObject pero en otro cargador de clases):

Object o = ... 
Gson gson = new Gson(); 
YourObject yo = gson.fromJson(gson.toJson(o), YourObject.class); 

que utiliza esta solución porque puedo compilar cualquier código Java de una WebApp (en Tomcat). Esta solución alternativa se ejecuta en producción.

3

Si la clase que deben ser echado implementa Serializable a continuación:

private <T> T castObj(Object o) throws IOException, ClassNotFoundException { 
    if (o != null) { 
     ByteArrayOutputStream baous = new ByteArrayOutputStream(); 
     { 
      ObjectOutputStream oos = new ObjectOutputStream(baous); 
      try { 
       oos.writeObject(o); 
      } finally { 
       try { 
        oos.close(); 
       } catch (Exception e) { 
       } 
      } 
     } 

     byte[] bb = baous.toByteArray(); 
     if (bb != null && bb.length > 0) { 
      ByteArrayInputStream bais = new ByteArrayInputStream(bb); 
      ObjectInputStream ois = new ObjectInputStream(bais); 
      T res = (T) ois.readObject(); 
      return res; 
     } 
    } 
    return null; 
} 

uso:

Object o1; // MyObj from different class loader 
MyObj o2 = castObj(o1); 
+0

Solo quiero señalar que el uso de la serialización es equivalente a los campos de copia profunda entre objetos. Sin embargo, no "copia" cuerpos de métodos. Así que MyObj (A) cargado por el cargador de clases principal y MyObj (B) recuperado del cargador personalizado son diferentes y la mayoría de los campos coinciden. – Vortex

Cuestiones relacionadas