Strutsの最近のブログ記事

 現在常駐先では、あるシステムで有名なフレームワークを利用してスタンドアロンのプログラムを作成しているのですが、Webのプログラムを担当している方に「Webでも何か(独自の)フレームワークを利用しているのですか。」と聞いてみました。その方が担当している部分だけは独自のフレームワークを利用していないとの事でしたが、Javaで一般的なStrutsを利用しているそうです。ただ今回のような(オフィスの1フローが埋まるぐらいのプログラマーがおり、数年規模の作業)プロジェクトでは、Strutsではまだ応用範囲が多いことから一般的にはその間にもプログラムの品質を統一する独自又は一般的に使用されているフレームワークを使用するそうです。プロジェクトが大規模になればなるほどプログラマーも増え、関わる会社も多くなりますからより中間的なフレームワークが必要ということだと思います。

Struts自体がフレームワークであることや他の言語(例えばCなど)と比較すると色々と制約が多いと考えていましたが、確かにStrutsでもコーディング規約をより明確にしないとプログラマーの差でコードの品質が一定に保つのは難しいそうです。アルバイトや小規模な社内システムしか作成した事が無い私としては、大変参考になりました。


 私向きの資格発見!!で番組を紹介しましたが、放送の中で会社ごとに整理するルール(特に、場所)を徹底することによって道具を探す手間を省き生産性を向上させる(目からウロコの整理術)という内容があったのですが、これはまさしく、プログラミングで言うフレームワークに相当するのではないでしょうか。ただ、プログラミングではフレームワークを当たり前のように利用していますが、私が感じるのは、仕事は出来ていてもプライベートに反映されていない事です。例えば、システムの設計でプロジェクト進捗を管理する事は出来ますが、自分のプライベートの事になると計画が一切されていない事などです。私は、仕事の技をプライベートなどの生活で利用したり、プライベートで行っていることや仕事とは関係なく勉強したことを他の人よりは仕事でも生かしていると思っています。例えば、一般的な整理術をプログラミングにも応用するというようにです。又、それは今までコンピュータが導入されていなかった所にコンピュータを導入することと同じでもあると思いませんか。仕事で依頼者(お客)の問題を解決すること自体が出来ると言うことは、プログラミング(一般的にはシステム設計)と他の部分(例えば、勤怠管理などの管理や整理)が共通していることだと思います。おなたは、ちゃんと生かし切れていますか。

 LifeHackが叫ばれていますが、プログラマーやSEはまさにコンピュータの利用を中心にした仕事をしているのですから、普段からフレームワークを利用するプログラマーも多いと思いますが、普段から利用しているからこそ、他にも応用が効かないか考えてみるのも仕事の効率や品質だけではなくプライベートを充実する為にも良いかもしれません。

前回はDJUnitを使った単体試験を紹介しましたが、今回はWEBアプリケーションでよく使われるStrutsのアクションクラスのテストを楽に行うライブラリを紹介いたします。
StrutsTestCaseを利用することによってアクション単位にテストすることが可能でブラウザを開かなくても試験ができます。今まではアクションの試験は実際にブラウザを動かす必要があったため面倒なものでした。さらに画面から入力される情報をプログラム上で作成するので未入力でボタンを押したなどのバリデートのテストも同時行えます。

 

StrutsTestCase for JUnit
http://sourceforge.net/project/showfiles.php?group_id=39190

cactus(今回はStrutsTestCaseの中にあるjarを使いますので落とさなくても良いです)
http://jakarta.apache.org/cactus/downloads.html

【環境・バージョン情報】
OS:WindowsXP SP3
Eclipse:3.3.2
JDK:JDK1.5.0
StrutsTestCase:strutstest214-1.2_2.4.zip
Tomcat:6.0.16
struts:1.2
 

すでにhttp://localhost:8080/sampleでログイン処理後(セッションにユーザID格納)、一覧ページを開くような処理が実装されているとします。

 

StrutsTestCase for JUnitのページからダウンロードしたstrutstest214-1.2_2.4.zipのexamples\test.warに入っているjarの中身を利用します。

【新たに追加したLIB】
aspectjrt-1.2.1.jar
cactus-13-1.7.jar
commons-collections.jar
commons-httpclient-2.0.jar
junit-4.0.jar
strutstest-2.1.4.jar

 

WEB.xmlに記述を追加します。

【WEB.xmlの追加】
    <!-- Cactus Servlet Redirector configuration -->
    <servlet>
        <servlet-name>ServletRedirector</servlet-name>
        <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>
    </servlet>

    <!-- Cactus Servlet Redirector URL mapping -->
    <servlet-mapping>
        <servlet-name>ServletRedirector</servlet-name>
        <url-pattern>/ServletRedirector</url-pattern>
    </servlet-mapping>

 

以下のように記述したcactus.propertiesをsrc直下に置きコンパイルすればclassesにコピーされます。

【cactus.propertiesの中身】
cactus.contextURL = http://localhost:8080/sample
cactus.servletRedirectorName = ServletRedirector
cactus.enableLogging=true
 

そこで以下のようなテストプログラムを作成して、Tomcatプラグインを起動しつつ、JUnitで実行してみると

StrutsTestCaseを使って楽にアクションのテストを行う1.JPG

StrutsTestCaseを使って楽にアクションのテストを行う2.JPG

と無事にJUnitが完了できました。

setUpメソッドにログイン処理を書くことができることによって、ログインした状態で各ページのテストを実行することが可能です。これはわざわざブラウザでログインしないでテストできるので、シナリオを作ってテストするようなツールで実行するより早くテストが実行できるかもしれません。

 

最終的にはJSPも含めたブラウザからのテストをしないといけないのですが、アクションのテスト結果も出せなどと言われた場合や、余裕があるプロジェクトでは利用する価値はあるのではないでしょうか。

JSPでVMの制限に出会う

|

Strutsベースでの開発での不具合対応のヘルプとして対応しているときのことです。


機能追加でボタンを画面に一つ追加したところ(logicタグでenable、disableのボタンを配置)、java.lang.ClassFormatError: fooJspClass (Code of a method longer than 65535 bytes)というエラーが!
直訳すると「メソッド一つのコードは64kbyteを越えている」とあり、なぜ64k?と思い調べてみるとJava VMの制限でclassファイルに変換された時、1メソッドのコードが64kbyteを越えてしまうとclassファイルにコンパイルはできているが実行できない模様。

具体的に何でこんなにコード数を増やしているのかとJSPが一度Javaファイルに変換されたファイルをworkフォルダ以下で見ていると

1.JSPの_jspService()メソッドでout.write()でJSPに書かれているHTMLの書式をすべて書いている。インデントのためのタブ、改行、コメントなどすべてがout.write()で書かれている。

2.2ヶ月分の日付の情報を入力するフィールドが存在しており、それがList型で取得しておらず、一つ一つFormの変数として作り込まれており、繰り返しで対応できるところが62回(31日×2)変数名を変えてJSPに書き込まれており、これによってコード数が跳ね上がっている。

と原因がわかりました。

対応方法としてJSPの_jspService()メソッドはJSPファイルをコンパイルしたときにできるメソッドなのでメソッドを複数に分けることもできず、かといってほとんどテストが終わっている画面なので仕組み自体を変えて再テストというのは時期としてよろしくなく、タブや改行、コメントを省けるところは省いて今回必要な機能追加してみて64kの制限を超えないようにと対応しました。

とても内容が多いHTMLを書くというのはそんなのある機会ではないのですが、どうしようもなく作成することがあるかもしれません。日頃はstrutsのtilesなどでJSPを分割しておくのが予防策にはなるのではないでしょうか。

DynaActionFormの継承(2)

|

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

 

 

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

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

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

こんなんで行ける様子。

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

 

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

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

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で紹介されるはずですよ。