◀ 3.

Struts1 の ActionForm とは? validate() / reset() / DynaActionForm の使い分け

▶
この記事の要点
  • ActionForm は HTTP リクエストパラメータを Java Bean に自動マッピングする Struts1 のクラス
  • <input name="userId"> → setUserId(String) がフレームワーク側で自動呼出
  • validate() は ActionErrors を返し、エラーがあればinputへ戻る。パスかforward名かは設定による
  • reset()は標準の値設定処理の前に呼ばれる。必要な初期化を自分で実装し、未送信のチェックボックスにも対応する
  • POJO を書く代わりに DynaActionForm / DynaValidatorForm で xml だけで定義可能
  • Spring MVC では @ModelAttribute + Bean Validation (@Valid) に相当

以下は保守終了したStruts 1の既存アプリを読むための説明です。フォーム取得、初期化、値のバインド、入力検証、Actionでの認証・業務処理を分けて確認します。入力形式の検査に通ったことは本人確認や操作権限の確認ではありません。

フォーム取得からreset、値設定、validateとinputへの分岐

ActionForm とは

org.apache.struts.action.ActionForm は Struts1 でフォーム入力値を保持する Java Beanの基底クラスです。HTML フォームの name 属性と Bean のプロパティ名を一致させると、Struts が自動的に setter を呼んでフィールドに値を詰めてくれます。

定義の例

次のresetはrememberMeだけをfalseにする例です。userId/passwordを自動で空にするものではありません。未送信項目を何に戻すかはフォームの用途・保持範囲ごとに決めます。validate内の長さ検査も説明用の方針であり、既存利用者の認証条件をこの例で変更しないでください。

package com.example.form;

import org.apache.struts.action.*;
import javax.servlet.http.HttpServletRequest;

public class LoginForm extends ActionForm {
    private String userId;
    private String password;
    private boolean rememberMe;

    @Override
    public void reset(ActionMapping mapping, HttpServletRequest request) {
        // チェックボックスは送信されないと値が来ないので明示初期化
        this.rememberMe = false;
    }

    @Override
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
        ActionErrors errors = new ActionErrors();
        if (userId == null || userId.trim().isEmpty()) {
            errors.add("userId",
                new ActionMessage("error.userId.required"));
        }
        if (password == null || password.length() < 8) {
            errors.add("password",
                new ActionMessage("error.password.length", "8"));
        }
        return errors;
    }

    // --- getter / setter ---
    public String getUserId() { return userId; }
    public void setUserId(String v) { this.userId = v; }
    public String getPassword() { return password; }
    public void setPassword(String v) { this.password = v; }
    public boolean isRememberMe() { return rememberMe; }
    public void setRememberMe(boolean v) { this.rememberMe = v; }
}

JSP からのバインディング

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:form action="/login" focus="userId">
  <p>ユーザ ID: <html:text property="userId" size="20" /></p>
  <p>パスワード: <html:password property="password" size="20" /></p>
  <p><html:checkbox property="rememberMe" /> ログインを記憶</p>
  <p><html:submit value="ログイン" /></p>
</html:form>

<%-- エラー表示 --%>
<html:errors />

StrutsのJSP入力タグのproperty属性が Bean のプロパティ名(getter/setter)と一致している必要があります。

struts-config.xml での宣言

<form-beans>
  <form-bean name="loginForm"
             type="com.example.form.LoginForm" />
</form-beans>

<action-mappings>
  <action path="/login"
          type="com.example.action.LoginAction"
          name="loginForm"
          scope="request"
          input="/login.jsp"
          validate="true">
    <forward name="success" path="/home.jsp" />
  </action>
</action-mappings>

ライフサイクル

順序処理備考
1ActionForm のインスタンス取得指定scopeの属性を検索し、互換性のある既存Formがあれば再利用。requestでも同一要求内のforward等で既存Formがあり得る
2reset(mapping, request) 呼出標準の値設定処理の前に呼ぶ。どの項目を初期化するかはresetの実装による
3setter で値設定(自動)HTTP パラメータ → Bean
4validate(mapping, request) 呼出validate="true" の場合のみ
5aエラーあり → inputのパスまたはforward名へexecute() はスキップ
5bエラーなし → Action#execute()第 2 引数で Form 受け取り

DynaActionForm: Bean クラスを書かない方法

フォーム項目のBeanクラスを省略できることと、設定やAction・JSP・入力検証が不要なことは別です。get(String)は未定義名や型違いを実行時に検出するため、XMLのform-property名・型とActionのキャストを照合します。次のAction断片の...は省略記号で、そのままコンパイルする完成コードではありません。

フォーム項目が多いと Bean を書くのが面倒。DynaActionForm は xml だけで定義できます:

<form-beans>
  <form-bean name="userForm"
             type="org.apache.struts.action.DynaActionForm">
    <form-property name="userId"   type="java.lang.String" />
    <form-property name="age"      type="java.lang.Integer" initial="0" />
    <form-property name="email"    type="java.lang.String" />
    <form-property name="hobbies"  type="java.lang.String[]" />
  </form-bean>
</form-beans>

Action 側でのアクセス:

public ActionForward execute(ActionMapping mapping, ActionForm form, ...) {
    DynaActionForm df = (DynaActionForm) form;
    String userId = (String)  df.get("userId");
    Integer age   = (Integer) df.get("age");
    String[] hobbies = (String[]) df.get("hobbies");
    // ...
}

DynaValidatorForm + Validator フレームワーク

validation.xmlだけ置いても有効にはなりません。使用するform-beanの型、ValidatorPlugIn、validator-rules.xml/validation.xmlの読込、ルール名、メッセージ定義、Actionのvalidate設定を一組で確認します。DynaValidatorFormの宣言的検証も本人確認・認可の代わりにはなりません。

DynaValidatorForm + Commons Validator なら xml で検証ルールも宣言できます:

<!-- validation.xml -->
<form-validation>
  <formset>
    <form name="userForm">
      <field property="userId" depends="required,minlength">
        <arg position="0" key="label.userId" />
        <var><var-name>minlength</var-name><var-value>4</var-value></var>
      </field>
      <field property="age" depends="required,integer,intRange">
        <arg position="0" key="label.age" />
        <var><var-name>min</var-name><var-value>0</var-value></var>
        <var><var-name>max</var-name><var-value>150</var-value></var>
      </field>
    </form>
  </formset>
</form-validation>

使い分け

種類用途メリットデメリット
ActionFormActionForm派生Beanを実装IDE 補完、複雑な validate 可クラスが増える
DynaActionFormxml 宣言のみBean クラス不要get(String) でキャスト必要
DynaValidatorFormDyna + Validator検証も宣言的宣言だけで表せない業務条件は別処理・拡張を検討
ValidatorActionFormValidator対応のActionForm派生クラス両方の利点対象クラスの検証ルールの選択方法と設定を別途確認

Spring MVC との比較

以下は概念比較で、同等な検証を再現する完成例ではありません。 @Size(min=8)はnullを有効とします。元のStruts例はpasswordがnullならエラーなので、同じ必須条件を保つには@NotNull等も必要です。空白の許否は別に決め、@NotBlankを加えると条件が変わる点にも注意します。検証依存・imports・getter/setter・認証処理は省略されています。

// Spring MVC 版 (Struts1 ActionForm + validate に相当)
public class LoginDto {
    @NotBlank
    private String userId;
    @Size(min = 8)
    private String password;
    private boolean rememberMe;
    // getter/setter ...
}

@Controller
public class LoginController {
    @PostMapping("/login")
    public String login(@Valid @ModelAttribute LoginDto dto,
                        BindingResult br,
                        Model model) {
        if (br.hasErrors()) {
            return "login";  // input 属性に相当
        }
        // ...
        return "redirect:/home";
    }
}

FAQ

Q: チェックボックスの値が消える
A: 未チェック時はパラメータ自体が送信されません。reset() で false 初期化するのが Struts1 の定型パターン。

Q: validate() が呼ばれない
A: 該当Actionのvalidate設定、Formが取得できているか、標準処理が通っているかを確認します。inputの未指定はvalidateを呼ばない理由ではなく、検証エラー時に戻り先がなくなる問題です。キャンセル・独自processorの処理も区別します。

Q: 入れ子の Bean をマッピングできる?
A: html:text property="address.zip" のようにドット記法可。ただし Bean 側で getAddress().setZip() の経路が必要。

未送信と空文字を分ける

要求今回のreset例での結果
rememberMe=trueを送信resetでfalseにした後、値設定でtrueになる。
rememberMeを送信しない値設定で上書きされないため、resetのfalseが残る。
userIdを送信しないこのresetはuserIdを消さない。再利用Formなら前の値が残り得る。
userIdを空文字で送信setterへ空文字を設定する。パラメータ未送信とは同じではない。

sessionスコープを使う場合は複数タブや連続送信の値保持も確認してください。scope変更をエラーを消すだけの対処にせず、要求間で何を保持する設計かを先に整理します。要求値をそのまま権限や本人確認の根拠にしないでください。

inputの解決と修正後の確認

標準RequestProcessorは検証エラーがあるとAction実行へ進まず、ControllerConfigのinputForwardがfalseならinputをパス、trueならforward名として解決します。戻り先の存在、メッセージ表示、値の再表示を確認します。validate=false、Form未取得、キャンセル許可、独自processor等の経路を「どの要求も必ず同じ」と一般化しません。

Struts 1.3.10の単体でForm再利用・reset→値設定・未送信/空文字・validate無効・input未指定時の検証実行を確認しました。JSP表示や実ログイン全体、Commons Validatorの設定全体を動作保証するものではありません。

一次資料: Apache Struts 1.3.10 RequestUtils/RequestProcessorの公式ソース、Bean Validation 2.0のSize定義、Struts 1保守終了案内。設定はstruts-config.xml、実行側はActionも参照してください。

子ページ

子ページはありません

同階層のページ
  1. 導入方法
  2. struts-config.xmlの説明
  3. ActionForm
  4. Action
  5. エラー一覧
  6. JSP タグ
  7. カスタムタグ(tablib)