2010-05-12 25 views
7

Je suis en train de changer le chargeur de classe lors de l'exécution:changement 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(); 
    } 
} 

et:

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

(InjectingClassLoader est une extension de la classe org.apache.bcel.util .ClassLoader qui devrait charger les versions modifiées des classes avant de lui demander son parent)

e pour rendre le résultat de "approche 1" et "approche 2" exactement le même, mais il ressemble à thread.setContextClassLoader (classLoader) ne fait rien et la "approche 2" utilise toujours le classloader du système (peut être déterminée en comparant tcl et ccl variables lors du débogage).

Est-il possible de faire toutes les classes chargées par le nouveau thread utilisé classloader?

Répondre

12

La classe anonyme que vous créez via new Thread("test") { ... } comporte une référence implicite à l'instance englobante. Les littéraux de classe dans cette classe anonyme seront chargés en utilisant ClassLoader de la classe englobante.

Pour que ce test fonctionne, vous devez extraire une implémentation Runnable appropriée et la charger de manière réfléchie à l'aide du ClassLoader souhaité. puis passez cela explicitement au thread. Quelque chose comme:

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: "réfléchie". Étant donné qu'il y a confusion dans la question concernant le chargeur de classe de contexte, vous pouvez mentionner que setContextClassLoader n'a d'effet que si le thread effectue une opération qui nécessite de le définir (par exemple, créer un SAXParser, effectuer une recherche JNDI, etc.). –

+0

Fixé; réflexivement -> réfléchi. Merci. –

1

Je pense que InjectingClassLoader peut être important ici. Souvenez-vous du fonctionnement de la délégation de classe - si plus d'un chargeur de classe de votre hiérarchie trouve la classe, le classloader le plus haut sera celui qui charge. Comme InjectingClassLoader ne spécifie pas de parent dans son constructeur, il le fera par défaut au constructeur dans le ClassLoader abstrait, ce qui positionnera le chargeur de classe de contexte actuel en tant que parent de InjectingClassLoader (voir Figure 21.2 here)

Par conséquent, puisque le parent (ancien classloader de contexte) peut trouver TestProxy, il charge toujours la classe avant que InjectingClassLoader n'en ait l'opportunité.

+0

OK, désolé ... Il est pertinent de la manière que le InjectingClassLoader est une extension de la classe org.apache.bcel.util.ClassLoader (avec la méthode modifyClass mis en œuvre) qui fait des trucs méchant avec la classe avant qu'elle soit chargée. AFAIK org.apache.bcel.util.ClassLoader remplace le comportement par défaut de la chaîne classloaders de la manière dont la classe modifiée est chargée avant l'utilisation du classloader parent. – Chris

+0

Juste vérifié la source et org.apache.bcel.utilClassloader étend encore java.lang.ClassLoader ... – Greg

+1

Le constructeur de chargeur de classe par défaut utilise ClassLoader.getSystemClassLoader(), pas Thread.currentThread(). GetContextClassLoader(), en tant que parent chargeur de classe. –