2010-05-12 20 views
7

Sto cercando di cambiare il caricatore di classe in fase di esecuzione:Change classloader

public class Test { 
    public static void main(String[] args) throws Exception { 
     final InjectingClassLoader classLoader = new InjectingClassLoader(); 
     Thread.currentThread().setContextClassLoader(classLoader); 
     Thread thread = new Thread("test") { 
      public void run() { 
       System.out.println("running..."); 
       // approach 1 
       ClassLoader cl = TestProxy.class.getClassLoader(); 
       try { 
        Class c = classLoader.loadClass("classloader.TestProxy"); 
        Object o = c.newInstance(); 
        c.getMethod("test", new Class[] {}).invoke(o); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
       // approach 2 
       new TestProxy().test(); 
      }; 
     }; 
     thread.setContextClassLoader(classLoader); 
     thread.start(); 
    } 
} 

e:

public class TestProxy { 
    public void test() { 
     ClassLoader tcl = Thread.currentThread().getContextClassLoader(); 
     ClassLoader ccl = ClassToLoad.class.getClassLoader(); 
     ClassToLoad classToLoad = new ClassToLoad(); 
    } 
} 

(InjectingClassLoader è una classe che estende la org.apache.bcel.util .ClassLoader che dovrebbe caricare le versioni modificate delle classi prima di chiedere il genitore per loro)

I'd lik per rendere il risultato di "approccio 1" e "approccio 2" esattamente identico, ma sembra thread.setContextClassLoader (classLoader) non fa nulla e "approccio 2" utilizza sempre il classloader di sistema (può essere determinato confrontando tcl e le variabili ccl durante il debugging).

E 'possibile effettuare tutte le classi caricate dal nuovo thread utilizzare il classloader specificato?

risposta

12

La classe anonima che si sta creando tramite new Thread("test") { ... } ha un riferimento implicito all'istanza che la include. I valori letterali di classe all'interno di questa classe anonima verranno caricati utilizzando ClassLoader della classe in allegato.

Per fare in modo che questo test funzioni, è necessario estrarre un'implementazione Runnable appropriata e caricarla in modo riflessivo utilizzando il ClassLoader desiderato; quindi passalo esplicitamente al thread. Qualcosa di simile:

public final class MyRunnable implements Runnable { 
     public void run() { 
      System.out.println("running..."); 
      // etc... 
     } 
    } 

    final Class runnableClass = classLoader.loadClass("classloader.MyRunnable"); 
    final Thread thread = new Thread((Runnable) runableClass.newInstance()); 

    thread.setContextClassLoader(classLoader); // this is unnecessary unless you you are using libraries that themselves call .getContextClassLoader() 

    thread.start(); 
+1

Nit: "reflectively". Dato che c'è confusione nella domanda riguardante il caricatore di classi di contesto, si potrebbe menzionare che setContextClassLoader non ha alcun effetto a meno che il thread non esegua alcune operazioni che richiedono di essere impostato (ad esempio, creando un SAXParser, eseguendo una ricerca JNDI, ecc.). –

+0

riparato; riflessivamente -> riflessivamente. Grazie. –

1

Penso che InjectingClassLoader possa essere importante qui. Ricorda come funziona la funzione di caricamento in classe delle deleghe: se più di un classloader nella tua gerarchia riesce a trovare la classe, verrà caricato il classloader più in alto. (Vedere la Figura 21.2 here)

Poiché InjectingClassLoader non specifica un genitore nel suo costruttore, verrà impostato automaticamente sul costruttore nel ClassLoader astratto, che imposterà il classloader di contesto corrente come genitore di InjectingClassLoader. Pertanto, poiché il genitore (vecchio classloader di contesto) può trovare TestProxy, carica sempre la classe prima che InjectingClassLoader abbia la possibilità di farlo.

+0

OK, mi dispiace ... È rilevante nel modo in cui l'InjectingClassLoader è una classe che estende l'org.apache.bcel.util.ClassLoader (con il metodo modifyClass implementato), che lo rende un po 'di cose brutte con la classe prima che venga caricata. AFAIK org.apache.bcel.util.ClassLoader sta sovrascrivendo il comportamento predefinito della catena dei classloader nel modo in cui la classe modificata viene caricata prima che venga utilizzato il classloader genitore. – Chris

+0

Appena verificato l'origine e org.apache.bcel.utilClassloader estende ancora java.lang.ClassLoader ... – Greg

+1

Il costruttore del caricatore di classi predefinito utilizza ClassLoader.getSystemClassLoader(), non Thread.currentThread(). GetContextClassLoader(), come genitore caricatore di classe. –