2008年2月アーカイブ

DynaActionFormの継承(2)

|

DynaActionFormの継承で、「Struts標準で対応して欲しい」とか書きましたが

 

 

・・・既にありましたね。

Struts1.3から出来るようになっているのでした。

<form-bean name="DataForm" extends="BaseForm">

こんなんで行ける様子。

気をつけなくてはいけないのは、属性に type="" が無いことぐらいです。

 

 うーん。。。まぁ、以前の記事のは多重継承が出来る、ってぐらいですか。。

業務でどうしてもStruts 1.2系以下を使わなくてはいけないときには利用価値がありそうですねw

tomcatでとほほ

|

現在動いているシステムを別のサーバで稼動させるべく作業していたのですが。
折角なんで、5.5系で最新の5.5.26にしちゃおうかなと思って、ダウンロード。。

チョコチョコとインストールして。。。

おー、動いた動いた。

早速アプリケーションをデプロイしないと。

managerからwarを指定して、配備開始!

って、500エラー?

java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream

って何?

なんで?

んで、元のサーバで動いていた5.5.25と比較すると。。

managerのlibのcommons-fileuploadが1.0から1.2へ変更になってるね。
って、DeferredFileOutputStreamがなくなってますよ!

仕方が無いのでcommons-io-1.4.jarを探してきて、lib以下に配置。

これで動くようになりましたよ。でも、こんなんでいいのかねぇ。。

また社長に「だからjavaは・・・」なんていわれてしまうそうですよ。。。


Javaによるプログラムで一番やっかないなのが、メモリーリークやスレッドの同期など負荷試験をしっかりしないとわからない問題です。ここでは、メモリーリークとボトルネックが簡単に調査するためにEclipseプラグインの「Profiler」について書いていきます。

 

環境

OS:WindowsXP

JDK:jdk1.5.0_01

Eclipse:3.1

Tomcat:5.5.25

Tomcatプラグイン:Sysdeo Tomcat Launcher Plugin 3.2 beta3

 

1.まずは「Profiler」プラグインをダウンロードする。

http://sourceforge.net/projects/eclipsecolorer/

 

2.「Profiler」プラグイン自体の開発は現在止まっており、Eclipse3.0までしか動作確認されていない。Eclipse3.1で動作させるためにパッチを当てる。(※この手順とすっ飛ばすと3.1では動きません。)

http://sourceforge.net/tracker/index.php?func=detail&aid=1202373&group_id=48823&atid=454283

上記URLの一番下のprofiler_jars.part1.rar~profiler_jars.part4.rarをすべてダウンロードし、解凍。
解凍した中のprofiler_trace.jar、profiler_ui.jarをプラグイン「Profiler」フォルダへ上書きコピーする。

 

3.プラグインのProfilerフォルダにあるProfilerDLL.dllをJAVA_HOME\JRE\BIN\にコピーする。

4.Eclipseのパッケージ・エクスプローラー上でTomcatプロジェクトを右クリック→実行→構成および実行する。

 Profilerをダブルクリック、画面右のタブがメインのプロジェクトをプロジェクト名、メインクラスをorg.apache.catalina.startup.Bootstrapに設定する。

 タブが引数ではプログラム引数をstart、VM引数を-Dcatalina.base=<tomcat directory> -Dcatalina.home=<tomcat directory>。

 タブがクラスパスでは、ユーザー・エントリーにJAVA_HOME\LIB\tools.jarと<tomcat directory>\BIN\bootstrap.jarxを外部jarの追加。

 

5.4で作成した「Profiler」の実行を押せばTomcatが立ち上がり、「Profiler」プラグインの実行を確認できる。

 

DynaActionFormの継承

|

DynaActionFormで基底ActionFormを作っちゃおう!
の記事を受けまして、改良版です。

基本的には上記の記事と変わらず、DynaActionFormとActionServletを継承したクラスによりstruts-config.xmlの"includes"プロパティによって初期化処理時に継承していきます。

上記の記事で複数回継承できないのは、"includes"の有無を1度しか判定しないことと、"includes"自体をDynaActionFormのプロパティとして読み込んでしまうので、同名のプロパティが2つ定義されているのと同じ状態になることが原因でした。

そこで

DynaActionFormの継承クラスのinitialize()を

    public void initialize(ActionMapping mapping) {
        super.initialize(mapping);

        String name = mapping.getName();

        // フォームBEAN設定を取得
        FormBeanConfig config = mapping.getModuleConfig().findFormBeanConfig(name);

        // "includes"プロパティがあった場合、initialに指定されたフォームのプロパティを継承
        FormPropertyConfig propConfig = config.findFormPropertyConfig("includes");
        if (propConfig != null) {
            // get the initial values
            setFormPropertyMap(mapping, propConfig);
        }
    }

こんな感じに修正し、setFormPropertyMap()メソッドを追加

    private void setFormPropertyMap(ActionMapping mapping, FormPropertyConfig propConfig) {
        String formBeanToIncludes = propConfig.getInitial();
        if (formBeanToIncludes != null) {
            String[] includeForms = formBeanToIncludes.split(",");
            for (String includeForm : includeForms) {
                FormBeanConfig formBeanConfig = mapping.getModuleConfig().findFormBeanConfig(includeForm);

                if (formBeanConfig != null) {
                    FormPropertyConfig includes = formBeanConfig.findFormPropertyConfig("includes");

                    //"includes"プロパティがあった場合は再帰呼び出し
                    if(includes != null){
                        setFormPropertyMap(mapping, includes);
                    }
                    //"includes"以外のプロパティのinitial値をセット
                    for (FormPropertyConfig prop : formBeanConfig.findFormPropertyConfigs()) {
                        if (!prop.getName().equals("includes")) {
                            if (prop.getInitial() != null) {
                                this.getMap().put(prop.getName(), prop.getInitial());
                            }
                        }
                    }
                }
            }
        }
    }

 

ActionServletの継承クラスも同様に

    protected ModuleConfig initModuleConfig(String prefix, String paths) throws ServletException {
        ModuleConfig config = super.initModuleConfig(prefix,  paths);
  
        //FromBeanConfigの取得
        FormBeanConfig[] fbs = config.findFormBeanConfigs();

        for (FormBeanConfig fb : fbs) {
            //DynaActionFromだった場合、"includes"プロパティに従い継承処理
            if (fb.getDynamic()) {
                //"includes"プロパティの取得
                FormPropertyConfig includes = fb.findFormPropertyConfig("includes");
                if (includes != null) {
                    setFormPropertyIncludes(includes, config, fb);
                }
                DynaActionFormClass.createDynaActionFormClass(fb);
            }
        }
        return config;
    }

こんな感じに修正し、setFormPropertyIncludes()メソッドを追加

    private void setFormPropertyIncludes(FormPropertyConfig includes, ModuleConfig config, FormBeanConfig fbs) {
        String formBeanToIncludes = includes.getInitial();
        if (formBeanToIncludes != null) {
            String[] includeForms = formBeanToIncludes.split(",");

            for (String formBeanToInclude : includeForms) {
                //"includes"プロパティのinitial値から継承元Fromを取得
                FormBeanConfig includeConfig = config.findFormBeanConfig(formBeanToInclude);
                //継承元フォームからプロパティ群を取得
                FormPropertyConfig[] props = includeConfig.findFormPropertyConfigs();
                for (FormPropertyConfig prop : props) {
                    //"includes"プロパティがあった場合、再帰呼び出し
                    if (prop.getName().equals("includes")) {
                        setFormPropertyIncludes(prop, config, fbs);
                    } else {
                        //既に同名のプロパティが登録されている場合は登録しない
                        if (fbs.findFormPropertyConfig(prop.getName()) == null) {
                            fbs.addFormPropertyConfig(prop);
                        }
                    }
                }
            }
        }
    }


ちなみにですが、setFormPropertyIncludes()メソッドの上の方に
String[] includeForms = formBeanToIncludes.split(",");
こんな記載があります。

"includes"のinitial値をカンマで区切ることで複数回継承するだけでなく多重継承も出来るようにしているのですねぇ。
DynaActionFormをクラスと捉えると、多重継承はしないほうがいい気もするのですが、
あまり継承が深くなるとstruts-config内で継承関係を追いかけるのが困難になりそうなのでこの形式にしました。

という訳で、struts-config.xmlはこんな感じ。

    <form-beans>

        <!-- 基底フォーム その1 -->
        <form-bean name="BaseForm1" type="org.apache.struts.action.DynaActionForm">
          <form-property name="base1" type="java.lang.String"  />
        </form-bean>

        <!-- 基底フォーム その2 -->
        <form-bean name="BaseForm2" type="org.apache.struts.action.DynaActionForm">
          <form-property name="base2" type="java.lang.String"  />
        </form-bean>

        <!-- 基底フォーム その3 (その1を継承)-->
        <form-bean name="BaseForm3" type="jp.co.ncad.common.BaseForm">
          <!-- 基底フォームその1をインクルード -->
          <form-property name="includes" type="java.lang.String" initial="BaseForm" />

          <form-property name="base3" type="java.lang.String"  />
          <form-property name="base4" type="java.lang.String"  />
        </form-bean>

        <!-- 基底フォームを継承するフォーム -->
        <form-bean name="DataForm" type="jp.co.ncad.common.BaseForm">
           <!-- 基底フォームをインクルード -->
          <form-property name="includes" type="java.lang.String" initial="BaseForm2,BaseForm3" />
         
          <form-property name="data1" type="java.lang.String"  />
          <form-property name="data2" type="java.lang.String"  />
        </form-bean>

    </form-beans>

 

これでDataFormは基底フォーム1,2,3のプロパティとDataForm自身のプロパティ
つまりbase1,base2,base3,base4,data1,data2のプロパティを持ったフォームとなります。

継承元Formと継承先Formに同名のプロパティがあっても、片方は無視されて重複しないようになっています。
両方にinitialが指定されている場合継承先Form(クラスで言えば子クラス側)のinitial値が有効になります。
さすがに同一のForm内に同名のプロパティがあるのは許容しませんが。


しかしこの機能、struts標準で対応してくれませんかねぇ。
<form-extends>タグとか用意したらいいのに・・・
と思ったんですが、

struts-configのdtdを書き換えて<form-extends>タグを追加出来るようにして
ModuleConfigとかFormBeanConfigとかもオーバーライドすれば実装できるんじゃなかろうか。

そのうち試しますので、うまく出来たらまた報告します。

Dyna系Formがお目見えしてからだいぶ経っていますが、その使い勝手の良さがいまひとつ見えてこない気がします。
getter/setterをいちいち書かなくても良いというのが最大の利点ですが、それすらもstruts-config.xmlにform-propertyを記述するという作業を考えればそれほどのメリットとは思えません。
むしろ、各Form間で共通なメンバーをいちいちform-propertyに宣言しなければならない(基底クラスが作成できない)というデメリットのほうが大きいように感じられます。

以前、(今時!?)ソースのステップ数により試験のボリュームが変わってくる案件の際に「いかにソースを小さくするか」ということをきっかけに「Dyna系を使えばFormが減るから」という理由でDynaを使った事がありました。
その際に初めに引っかかったのが、「基底クラスって作れないの?」でした。

まず、通常のActionFormとDynaActionFormをうまく同居させられないかと思ったのですが、これは無理でした。
そこでチョコチョコとググっている中で見つけましたよ。良い方法を。。

やり口としては、struts-config上に基底のform-beanを作ってそいつを各々のform-beanの初期化処理でメンバーに追加するって方法です。

まずはstruts-configへの記述方法ですが。

    <form-beans>

        <!-- 基底フォーム -->
        <form-bean name="BaseForm" type="org.apache.struts.validator.DynaValidatorForm">
          <form-property name="base1" type="java.lang.String"  />
          <form-property name="base2" type="java.lang.String"  />
        </form-bean>

        <!-- 基底フォームを継承するフォーム -->
        <form-bean name="DataForm" type="jp.co.ncad.common.BaseForm">
           <!-- 基底フォームをインクルード -->
          <form-property name="includes" type="java.lang.String" initial="BaseForm" />
         
          <form-property name="data1" type="java.lang.String"  />
          <form-property name="data2" type="java.lang.String"  />
        </form-bean>

    </form-beans>

上記の記述で
    base1
    base2
というメンバーをもつ基底フォームに
    data1
    data2
というメンバーが追加されたフォームの定義です。

継承するフォームはtypeに「jp.co.ncad.common.BaseForm」を指定しています。
この「jp.co.ncad.common.BaseForm」の初期化処理内で基底クラスの内容をマージしてます。

では、具体的にどの様にしてマージしているかですが。

1.ActionForm
DynaActionFormを継承したクラスを用意し、

public void initialize(ActionMapping mapping)

を以下の感じでオーバーライト。

    public void initialize(ActionMapping mapping) {
        super.initialize(mapping);

        this.mapping=mapping;
       
        //form bean name

        String name = mapping.getName();

        //form bean config

     FormBeanConfig config = mapping.getModuleConfig().findFormBeanConfig(name);

        //now check if there is "includes" property

        FormPropertyConfig propConfig = config.findFormPropertyConfig("includes");
        if (propConfig != null)
        {
              //get the initial values
        String formBeanToInclude = propConfig.getInitial();
        FormBeanConfig formBeanConfig =
        mapping.getModuleConfig().findFormBeanConfig(formBeanToInclude);
        FormPropertyConfig properties[] =
        formBeanConfig.findFormPropertyConfigs();
            for (int i = 0; i < properties.length; i++)
            {
                //set(properties[i].getName(), properties[i].initial());

                this.getMap().put(properties[i].getName(), properties[i].getInitial());
            }
        }         
    }


こいつをreset()メソッドで呼び出しておけばOKです。


2.ActionServlet
ActionServletを継承したクラスを用意し、

protected ModuleConfig initModuleConfig(String prefix, String paths)

っつう、メソッドを以下の感じにオーバーライト。

   protected ModuleConfig initModuleConfig(String prefix, String paths)
            throws ServletException
    {
       
        ModuleConfig config = super.initModuleConfig(prefix,  paths);
       
        // Force creation and registration of DynaActionFormClass instances
        // for all dynamic form beans we wil be using
        FormBeanConfig fbs[] = config.findFormBeanConfigs();
        for (int i = 0; i < fbs.length; i++)
        {
            if (fbs[i].getDynamic())
            {
                //check if this form bean config has includes property
                FormPropertyConfig includes = fbs[i].findFormPropertyConfig("includes");
                if (includes != null)
                {
                    String formBeanToInclude = includes.getInitial();
                    //find the form bean configuration for the form bean to include
                    FormBeanConfig includeConfig = config.findFormBeanConfig(formBeanToInclude);
                    //get all the properties of the form bean to include
                    FormPropertyConfig[] props = includeConfig.findFormPropertyConfigs();
                    for (int j = 0; j < props.length; j++)
                    {
                        FormPropertyConfig prop = props[j];
                        //now add this property as the part of original form bean
                        fbs[i].addFormPropertyConfig(prop);
                    }
                }
                DynaActionFormClass.createDynaActionFormClass(fbs[i]);
            }
        }
        return config;

    }


んで、web.xmlのservlet-classタグで上記で作成したActionServletを使う様にすれば。。。

あーら不思議、BaseFormを継承したDataFormが出来てしまいます。

これは便利ですねぇ。。

この記述だと一回の継承しか出来ないのですが、我が誇れる後輩くんが無限継承型に進化させているらしいので、きっとこのblogで紹介されるはずですよ。