Alterar carregador de classe
-
26-09-2019 - |
Pergunta
Estou tentando mudar o carregador de classes em tempo de execução:
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();
}
}
(InjetandoClassLoader é uma classe que estende o org.apache.bcel.util.ClassLoader que deve carregar as versões modificadas das classes antes de solicitá-las ao pai)
Eu gostaria de fazer com que o resultado da "abordagem 1" e da "abordagem 2" fosse exatamente igual, mas parece thread.setContextClassLoader(classLoader) não faz nada e a "abordagem 2" sempre usa o carregador de classe do sistema (pode ser determinado comparando as variáveis tcl e ccl durante a depuração).
É possível fazer todos classes carregadas por novo thread usam determinado carregador de classe?
Solução
A classe anônima que você está criando via new Thread("test") { ... }
tem uma referência implícita à instância envolvente.Os literais de classe dentro desta classe anônima serão carregados usando o ClassLoader da classe envolvente.
Para fazer este teste funcionar, você deve extrair uma implementação Runnable adequada e carregá-la reflexivamente usando o ClassLoader desejado;em seguida, passe isso explicitamente para o thread.Algo como:
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();
Outras dicas
Acho que InjectingClassLoader pode ser importante aqui.Lembre-se de como funciona a delegação de carregamento de classe - se mais de um carregador de classe em sua hierarquia puder encontrar a classe, o carregador de classe superior será aquele que carregará.(Ver Figura 21.2 aqui)
Como InjectingClassLoader não especifica um pai em seu construtor, ele será padronizado para o construtor no ClassLoader abstrato, que definirá o carregador de classe de contexto atual como pai de InjectingClassLoader.Portanto, como o pai (antigo carregador de classe de contexto) pode encontrar TestProxy, ele sempre carrega a classe antes que InjectingClassLoader tenha a chance.