2010-05-12 5 views
7

Ich versuche, die Class Loader zur Laufzeit wechseln:ändern 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(); 
    } 
} 

und:

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

(InjectingClassLoader ist eine Klasse Verlängerung der org.apache.bcel.util .ClassLoader, die die geänderten Versionen der Klassen laden sollten, bevor sie ihre Eltern für sie fragen)

würde ich lik e, um das Ergebnis von "Ansatz 1" und "Ansatz 2" genau gleich zu machen, aber es sieht aus wie thread.setContextClassLoader (classLoader) tut nichts und die "Approach 2" verwendet immer den System-Classloader (kann durch Vergleich tcl ermittelt werden und ccl Variablen während des Debuggens).

Ist es möglich, alle Klassen durch neue Thread-Verwendung gegeben Klassenlader gemacht zu machen?

Antwort

12

Die anonyme Klasse, die Sie über new Thread("test") { ... } erstellen, hat eine implizite Referenz auf die umschließende Instanz. Klassenliterale innerhalb dieser anonymen Klasse werden mit dem ClassLoader der umschließenden Klasse geladen.

Damit dieser Test funktioniert, sollten Sie eine geeignete Runnable-Implementierung auswählen und sie mit dem gewünschten ClassLoader reflektiv laden. Übergeben Sie das dann explizit an den Thread. Etwas wie:

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: "reflektierend". Da in der Frage zum Kontextklassenladeprogramm Verwirrung herrscht, können Sie erwähnen, dass setContextClassLoader keine Auswirkungen hat, es sei denn, der Thread führt eine Operation durch, die das Festlegen erfordert (z. B. Erstellen eines SAXParser, Ausführen einer JNDI-Suche usw.). –

+0

behoben; reflexiv -> nachdenklich. Vielen Dank. –

1

Ich denke, InjectingClassLoader kann hier wichtig sein. Denken Sie daran, wie die Klassenladedelegierung funktioniert - wenn mehr als ein Klassenlader in Ihrer Hierarchie die Klasse finden kann, wird der oberste Klassenlader geladen. (Siehe Abbildung 21.2 here)

Da InjectingClassLoader kein Parent im Konstruktor angibt, wird standardmäßig der Konstruktor im abstrakten ClassLoader verwendet, der den aktuellen Context-Klassenlader als InjectingClassLoader-Eltern definiert. Da das übergeordnete Element (alter Kontextklassenlader) TestProxy finden kann, wird die Klasse daher immer geladen, bevor InjectingClassLoader eine Chance erhält.

+0

OK, sorry ... Es liegt in der Art und Weise relevant, dass die InjectingClassLoader ist eine Klasse, den org.apache.bcel.util.ClassLoader (mit implementierter modifyClass Methode) erstreckt, die einige bösen Sachen macht mit die Klasse vor dem Laden. AFAIK org.apache.bcel.util.ClassLoader überschreibt das Standardverhalten der Klassenloader-Kette in der Art und Weise, wie die geänderte Klasse geladen wird, bevor der Eltern-Classloader verwendet wird. – Chris

+0

Nur überprüft, die Quelle und org.apache.bcel.utilClassloader erweitert immer noch java.lang.ClassLoader ... – Greg

+1

Der Standard-Klassenladerkonstruktor verwendet ClassLoader.getSystemClassLoader(), nicht Thread.currentThread(). GetContextClassLoader(), als übergeordnetes Element Klassenlader. –