HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Webアプリケーション開発の際の注意事項(4)

これまでの記事。

今度はコードを書くときの注意でもあり、使用しているライブラリに関連する問題でもある。
java.beans.PropertyEditorManager には、あるクラスに対するプロパティエディタ(java.beans.PropertyEditor インタフェースの実装クラス)を登録することができる。ここに問題がある。
検証のために、次のプログラムを使用する。

改造した core.Main は次のとおり。

  • アプリを停止した後、完全に停止しきらない場合にも次のアプリを起動できるように、アプリを別スレッドで起動する。
package core;

import java.io.File;
import java.lang.ref.Reference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {

    /**
     * 使用法:
     *   java core.Main <app-classpath><app-classpath> -- <app-class-name> <param-name-1> <param-value-1>
     * 
     *   <app-classpath>:
     *     アプリケーションのエントリポイントとなるクラスが含まれるディレクトリ
     *   <app-class-name>:
     *     アプリケーションのエントリポイントとなるクラスの名前
     *   <param-name-n>:
     *     パラメータ名
     *   <param-value-n>:
     *     パラメータの値
     */
    public static void main(String[] args) throws Exception {
        URL[] clsPath;
        int idx = 0;
        {
            List<URL> list = new ArrayList<URL>();
            for (; idx < args.length; ++idx) {
                if (args[idx].equals("--")) {
                    break;
                }
                list.add(new File(args[idx]).toURI().toURL());
            }
            clsPath = list.toArray(new URL[list.size()]);
        }
        String clsName = args[++idx];
        Map<String, String> params = new HashMap<String, String>();
        for (++idx; idx + 1 < args.length; idx += 2) {
            params.put(args[idx], args[idx + 1]);
        }

        test(clsPath, clsName, params);
        test(clsPath, clsName, params);
    }

    private static void test(final URL[] clsPath, final String clsName, final Map<String, String> params) throws MalformedURLException, Exception {
        Thread t = new Thread() {
            public void run() {
                try {
                    testImpl(clsPath, clsName, params);
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        t.join(5000);
    }

    private static void testImpl(URL[] clsPath, String clsName, Map<String, String> params) throws MalformedURLException, Exception {
        AppLauncher launcher = new AppLauncher(clsPath, clsName, params);
        launcher.initialize();
        launcher.destroy();
        Reference<ClassLoader> ref = launcher.getClassLoaderReference();

        /*
         * アプリケーションクラスローダーのインスタンスがGCで回収されるまで、
         * 待つ、待つ、待つ‥
         */
        System.out.println("[" + Thread.currentThread().getName() + "] " + "### sleep start");
        for (int i = 0; ref.get() != null; ++i) {
            System.out.println("[" + Thread.currentThread().getName() + "] " + "### sleep[" + i + "] start");
            System.gc();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace(System.out);
            }
            System.out.println("[" + Thread.currentThread().getName() + "] " + "### sleep[" + i + "] end");
        }
        System.out.println("[" + Thread.currentThread().getName() + "] " + "### sleep end");
    }

}

で、作成するアプリ実装は次のとおり。

package app;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;

public class AppImpl1 implements App {

    public void initialize() {
        PropertyEditorManager.registerEditor(MyBean.class, MyEditor.class);

        PropertyEditor editor = PropertyEditorManager.findEditor(MyBean.class);
        editor.setAsText("1:hoge");
        System.out.println(editor.getAsText());
    }

    public void destroy() {
    }

    public void setParameter(String name, String value) {
    }

}

MyBean クラスは次のとおり。

package app;

public class MyBean {

    private int id;

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

MyEditor クラス(java.beans.PropertyEditor の実装)は次のとおり。

package app;

import java.beans.PropertyEditorSupport;

public class MyEditor extends PropertyEditorSupport {

    public String getAsText() {
        MyBean myBean = (MyBean) getValue();
        return myBean.getId() + ":" + myBean.getName();
    }

    public void setAsText(String text) {
        MyBean myBean = new MyBean();
        int idx = text.indexOf(":");
        myBean.setId(Integer.parseInt(text.substring(0, idx)));
        myBean.setName(text.substring(idx + 1));
        setValue(myBean);
    }

}

で、これを実行すると次のような感じ。Ant の java タスクで fork="true" と timeout="30000" 指定つきで実行する。

===+ core.AppLauncher$1@9304b1
===- core.AppLauncher$1@9304b1
1:hoge
[Thread-0] ### sleep start
[Thread-0] ### sleep[0] start
[Thread-0] ### sleep[0] end
[Thread-0] ### sleep[1] start
[Thread-0] ### sleep[1] end
[Thread-0] ### sleep[2] start
[Thread-0] ### sleep[2] end
[Thread-0] ### sleep[3] start
[Thread-0] ### sleep[3] end
[Thread-0] ### sleep[4] start
===+ core.AppLauncher$1@173a10f
===- core.AppLauncher$1@173a10f
1:hoge
[Thread-1] ### sleep start
[Thread-1] ### sleep[0] start
[Thread-0] ### sleep[4] end
    (中略)
[Thread-0] ### sleep[29] start
Timeout: killed the sub-process
    at org.apache.tools.ant.taskdefs.Java.fork(Java.java:787)
    at org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:211)
        :

やはりアプリは停止しきらない。これを解決するには、アプリ実装クラスの destroy() メソッドで次のようにすればよい。

    public void destroy() {
        PropertyEditorManager.registerEditor(MyBean.class, null);
    }


ところで、自分自身で PropertyEditor を登録している場合はアプリ終了時に登録解除するように注意すればよいが、使用するライブラリ自体が PropertyEditor を登録している場合はそれを調べなければならない。徹底的に‥