2010-04-07 12 views
17

Come posso fare questo:lanciato attraverso il classloader?

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 
    } 
} 

cosa ho bisogno è per la JVM di prendere in considerazione l'istanza Foo da cl come se fosse un 'istanza di Foo dal classloader del codice in esecuzione.

ho visto questi approcci, nessuno di loro bene per me (l'esempio di cui sopra è un esempio di giocattolo):

  1. caricare la classe (o un'interfaccia separata) da un caricatore di classe che è un genitore di sia il codice chiamante e classloader creato
  2. serializzare e deserializzare l'oggetto.

risposta

20

non è possibile. L'identità della classe è composta dal nome completo e dal programma di caricamento classe.

Il casting di un oggetto in una classe con lo stesso nome caricato da caricatori di classi diversi non è diverso dal tentativo di trasmettere uno String a Integer, perché queste classi potrebbero essere completamente diverse nonostante abbiano lo stesso nome.

+1

Quindi, se non possiamo nemmeno lanciare queste nuove istanze, come possiamo utilizzarle? Se tutto ciò che possiamo fare è 'Object obj = cl.loadClass (" Foo "). NewInstance();', come chiamiamo i metodi della nuova istanza di Foo? – Pacerier

+1

@Pacerier: bene, è possibile utilizzare la riflessione. Ma un caso più pratico è quello di fare in modo che le classi estendano le classi o implementino le interfacce da un classloader padre nella gerarchia di delega che sono disponibili anche per il resto del codice. Ma nel codice di esempio, non v'è alcun programma di caricamento classe genitore (secondo parametro al costruttore è nullo) ... –

+0

Può la performance della seconda opzione più veloce rispetto all'utilizzo di riflessione? O è vero che utilizza internamente la riflessione comunque? – Pacerier

3

Forse qualcosa utilizzando interfacce e java.lang.reflect.Proxy sarebbero base alle proprie preferenze. Utilizzando uno che trova e invoca il metodo pertinente sulla classe di destinazione. (Nota, nessuna sicurezza mobile-codice che avete sarà girato attraverso se si esegue questa operazione.)

7

Ho appena trascorso gli ultimi due giorni alle prese con questo problema esatto e ho finalmente ottenuto intorno al problema utilizzando Java riflessione:

// '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[] {}); 
} 

Spero che questo aiuti qualcuno.

+1

Come possiamo farlo senza riflessione? Se ogni singolo metodo di ogni singolo oggetto caricato dal nostro classloader deve essere richiamato tramite la reflection, questo non comprometterebbe seriamente l'intero programma? – Pacerier

0

Questo è un vecchio post in cui sono arrivato perché volevo fare quasi la stessa cosa, ma una versione più semplice di esso ...

Funziona in realtà se sia Foo e la classe caricata (nel mio caso da un file di classe .java in un altro pacchetto) estendere la stessa classe, ad esempio, AbstractTestClass.

pezzi di codice:

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

In questo modo posso inserire la classe caricata in un ArrayList<AbstractTestClass>.

+0

si carica 'test' dallo stesso programma di caricamento classi. Inoltre, anche se si specifica un altro, avrà bisogno di essere un figlio della classe loader corrente o non sarebbe in grado di lanciare a AbstractTestClass – IttayD

+0

@IttayD hai ragione, è necessario estendere la classe genitore (Foo in la domanda). Non era il punto della domanda: "considerare l'istanza di Foo da cl come se fosse un'istanza di Foo"? – Aname

1

No possibile gettato in diversi classloader.

avete questo soluzione con GSON, ad esempio il cast di oggetti di YourObject (Object è una classe YourObject ma in altri classloader):

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

Io uso questa soluzione perché compilo qualsiasi codice Java in un WebApp (su Tomcat). Questa soluzione alternativa è in esecuzione.

3

Se classe che hanno bisogno di essere gettato implementa Serializable poi:

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

utilizzo:

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

Voglio solo sottolineare che l'uso della serializzazione è equivalente alla copia profonda dei campi tra gli oggetti. Tuttavia, non "copia" i corpi del metodo. Quindi MyObj (A) caricato dal classloader principale e MyObj (B) recuperato dal caricatore personalizzato sono diversi e solo la maggior parte dei campi corrisponde. – Vortex