cypher256's blog

Pleiades とか作った

JSP、JSTL、EL で定数を使う

今回のプロジェクトでは定数クラスは設計書から自動生成されるようにしました。でもせっかく定数クラス作っても、JSP ではベタ書きしていたり、JSP で定数使うのはおかしいとか言うエラい人もいたりしますが、やっぱり JSP でも定数使いたい場面はあります。例は悪いですけど、こんな。

public interface Flg {
    String ON = "1";
    String OFF = "0";
}
<td>${Flg.OFF}</td>
とか
<c:if test="${hogeFlg == Flg.ON}">

今まで、定数クラスごとにマップを作ってアプリケーション・スコープに登録したりしていたのですが、面倒なので自動登録してしまおうというのが、JSTLConstantsRegister です。どこかで聞いた言い回しですけど。これはルート・パッケージ配下のインターフェースか public static の値を JSP から使用可能にするものです。

// JSTL 定数レジスター・クラス
public class JSTLConstantsRegister extends ComponentAutoRegister {

    // 定数として登録する対象の型
    private static final List<Class<?>> targetTypes = new LinkedList<Class<?>>() {{
        add(String.class);
        add(Integer.class);
        add(Long.class);
        add(Enum.class);
        add(Boolean.class);
    }};

    // コンストラクタ
    public JSTLConstantsRegister(NamingConvention namingConvention) {
        for (String rootPackageName : namingConvention.getRootPackageNames()) {
            addClassPattern(rootPackageName, ".*");
        }
        addReferenceClass(getClass());
    }

    // 指定されたクラスの定数をアプリケーション・スコープに登録
    @Override
    protected void register(String className) {
        Class<?> clazz = ClassLoaderUtil.loadClass(ResourceUtil.getClassLoader(), className);
        BeanMap beanMap = new BeanMap();
        for (Field f : clazz.getFields()) {
            if (f.getDeclaringClass() != clazz) {
                continue;
            }
            if (!targetTypes.contains(f.getType())) {
                continue;
            }
            int mod = f.getModifiers();
            if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
                String key = f.getName();
                Object value = FieldUtil.get(f, null);
                beanMap.put(key, value);
            }
        }
        if (beanMap.size() > 0) {
            String shortClassName = ClassUtil.getShortClassName(className);
            ServletContext application = ServletContextUtil.getServletContext();
            application.setAttribute(shortClassName, beanMap);
        }
    }
}


あとは適当に app.dicon とかに。

<!-- JSTL 定数レジスターのコンポーネント登録 -->
<component class="hoge.framework.internal.JSTLConstantsRegister"/>

実際はショート・クラス名の重複や値の null チェックをしたほうがいいと思います。ちなみに BeanMap は指定したキーがない場合は IllegalArgumentException をスローしてくれます。あとルート・パッケージ配下以外のクラスを登録したい場合は、dicon で指定できるように汎用的に作るか、コンストラクタの中で直接登録します。例えば、ActionMessages の定数を使いたい場合は、こんな。

    // コンストラクタ
    public JSTLConstantsRegister(NamingConvention namingConvention) {
        for (String rootPackageName : namingConvention.getRootPackageNames()) {
            addClassPattern(rootPackageName, ".*");
        }
        addReferenceClass(getClass());
        // クラスを指定して追加
        register(ActionMessages.class.getName());
        register(・・・.class.getName());
    }
    <html:errors property="${ActionMessages.GLOBAL_MESSAGE}"/>

やっていることは非常に単純です。面倒なことは ComponentAutoRegister がやってくれています。面倒なことというのは Java が標準でロードされていないクラスを検索する機能を持たないため、定数登録したいクラスを自分で検索するとなると、それがどこにあるのか、ファイルとしてあるのか、jar や war にあるのかということを考慮しなければなりません。自分で実装したとしても検証が非常に困難です。更にこのクラス、ソースを見る限り S2Container に依存してなさそうなので、DI Container を使っていないプロジェクトでもライブラリ的な使い方ができそうです。ComponentAutoRegister を使うとクラスに対する色々な処理ができますね。そんな使い方すんなとか言われそうですけど。