スレッド毎にClassLoaderを用意してクラスパスを割り当てる

ThreadにはsetContextClassLoader()というメソッドがあって、スレッド毎にクラスローダを割り当てることができる。要するにスレッドを作るときにそのスレッド専用のjarファイルなりクラスパスを割り当てることができる。

これを確かめるためにクラスパスを引数に受け取り新しく起動するスレッドにそのクラスパスを割り当てるプログラムを書いた。

クラスパスを割り当ててスレッドを起動するクラス

URLClassLoaderを作ってクラスパスを追加する。作ったURLClassLoaderを起動するスレッドに設定する。

package ihiroky.context.sample;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;

public class ContextDispatcher {

    private List<Thread> threadList;
    
    public ContextDispatcher() {
        threadList = new LinkedList<Thread>();
    }
    
    public void dispatch(String threadName, String[] classPath, final String className) {
        try {
            URL[] urls = new URL[classPath.length];
            for (int i=0; i<urls.length; i++) {
                final String base = new File(classPath[i]).getCanonicalPath().replaceAll("\\\\", "/");
                StringBuilder urlBuilder = new StringBuilder(256);
                urlBuilder.append("jar:file:");
                if (base.charAt(0) != '/') { urlBuilder.append('/'); }
                urlBuilder.append(base).append("!/");
                urls[i] = new URL(urlBuilder.toString());
            }
            
            Thread t = Thread.currentThread();
            URLClassLoader loader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
            URL[] newUrls = loader.getURLs();
            for (URL url : newUrls) {
                // 起動するスレッドに追加されるクラスパス
                System.out.println(t.getName() + " additional url     : " + url.toString());
            }
            // 各クラスローダのインスタンスを確認
            System.out.println(t.getName() + " NewURLClassLoader : " + loader.toString());
            System.out.println(t.getName() + " SystemClassLoader : " + ClassLoader.getSystemClassLoader());
            
            Runnable r = new Context(className); 
            Thread thread = new Thread(r, threadName);
            thread.setContextClassLoader(loader);
            thread.start();
            threadList.add(thread);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void terminate() {
        for (Thread thread : threadList) {
            thread.interrupt();
        }
        threadList.clear();
    }

    public class Context implements Runnable {
        
        private String className;
        
        public Context(String className) {
            this.className = className;
        }
        
        public void run() {
            try {
                Thread t = Thread.currentThread();
                URLClassLoader loader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
                // このスレッドコンテキストでのクラスローダを確認
                System.out.println(t.getName() + " classloader : " + loader.toString());
                
                Class<?> clazz = Class.forName(className, true, loader);
                Executable exec = (Executable)clazz.newInstance();
                exec.execute();
            } catch (Throwable th) {
                th.printStackTrace();
            }
        }
    }
}
Executableは新しいスレッド上での処理呼出口を規定するインターフェース
package ihiroky.context.sample;

public interface Executable {
    public void execute() throws Exception;
}
クラスパスが追加されるスレッド上で動くロジック
package ihiroky.app;

import org.apache.log4j.Logger;

import ihiroky.context.sample.Executable;

public class Sample implements Executable {

    @Override
    public void execute() throws Exception {
        Thread t = Thread.currentThread();
        ClassLoader loader0 = Sample.class.getClassLoader();
        Logger logger = Logger.getLogger(Sample.class);
        ClassLoader loader1 = Logger.class.getClassLoader();
        // クラスローダの確認
        System.out.println(t.getName() + " Sample.class.getClassLoader() : " + loader0.toString());
        System.out.println(t.getName() + " Logger.class.getClassLoader() : " + loader1.toString());
        
        while ( ! Thread.interrupted()) {
            logger.info("hello.");
            Thread.sleep(3000);
        }
    }
}
で、アプリを起動するメイン
package ihiroky.context.sample;

public class Main {

    public static void main(String[] args) {
        try {
            if (args.length < 2) { System.exit(1); }
            String[] classPath = new String[args.length-1];
            for (int i=0; i<args.length-1; i++) {
                classPath[i] = args[i];
            }
            String execClass = args[args.length-1];
            ContextDispatcher dispatcher = new ContextDispatcher();
            dispatcher.dispatch("SampleThread", classPath, execClass);
            System.in.read();
            dispatcher.terminate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
起動

context.jar はContextDispatcher, Mainクラスが入っていて、app.jar はSampleクラスが入っている。起動時のクラスパスにはcontext.jarとカレントディレクトリ(log4j.properties)しか入っていないが、SampleThreadでは引数で指定したjarからクラスが読み込まれる。つまり、SampleThreadでnewされるクラスが新しく用意したクラスローダを利用している。

[ihiroky@puppypc:build]$ /app/java/bin/jar tvf context.jar | grep class
  2001 Fri Mar 20 10:37:50 JST 2009 ihiroky/context/sample/ContextDispatcher$Context.class
  3510 Fri Mar 20 10:37:50 JST 2009 ihiroky/context/sample/ContextDispatcher.class
   196 Fri Mar 20 08:32:20 JST 2009 ihiroky/context/sample/Executable.class
  1159 Fri Mar 20 08:50:42 JST 2009 ihiroky/context/sample/Main.class
[ihiroky@puppypc:build]$ /app/java/bin/jar tvf app.jar | grep class
  1629 Fri Mar 20 10:38:38 JST 2009 ihiroky/app/Sample.class
[ihiroky@puppypc:build]$ ls
app.jar  context.jar  log4j.properties
[ihiroky@puppypc:build]$ /app/java/bin/java -cp context.jar:. ihiroky.context.sample.Main app.jar ../../lib/log4j-1.2.15.jar ihiroky.app.Sample
main additional url     : jar:file:/home/ihiroky/workspaces/ClassLoader/Application/build/app.jar!/
main additional url     : jar:file:/home/ihiroky/workspaces/ClassLoader/lib/log4j-1.2.15.jar!/
main NewURLClassLoader : java.net.FactoryURLClassLoader@19821f
main SystemClassLoader : sun.misc.Launcher$AppClassLoader@7d772e
SampleThread classloader : java.net.FactoryURLClassLoader@19821f
SampleThread Sample.class.getClassLoader() : java.net.FactoryURLClassLoader@19821f
SampleThread Logger.class.getClassLoader() : java.net.FactoryURLClassLoader@19821f
 2009-03-20 11:18:39,072 INFO  Sample               - hello. [SampleThread]
 2009-03-20 11:18:42,075 INFO  Sample               - hello. [SampleThread]
 2009-03-20 11:18:45,078 INFO  Sample               - hello. [SampleThread]