【Spring MVC】で simple な form の作り方

## まえがき 
- シンプルを目指すので、&lt;form:form&gt; 形式のタグライブラリ不使用にしたい
 - form拡張タグは便利で良いのですが、独自の書き方を覚える手間があるので
- 自分で作る Form オブジェクトに値を入れたい（パラメータ個別にしない）
- エラーハンドリングもなるべくシンプルに
- **エスケープ処理は自前で** c:out または fn:escapeXml
- 試行錯誤して何とかなりそうなので忘備録として残します

## 環境
- Spring framework 4 (古い...)
- Bootstrap 3 (古い...)
- JDK 1.8
- JSP

## 作り方
- URL (例)
 - /contextPath/inputAction/ : Form 表示URL
 - /contextPath/nextAction/ : Form の post 先 URL
 - /contextPath/finishAction/ : 完了画面

### form 準備 (html/jsp)
```jsp:inputAction.jsp
		<form id="form" method="post" action="/contextPath/nextAction/" class="form-horizontal">
		<div class="row row-center">
			<div class="col-xs-10">
				<div class="form-group">
					<label for="param1" class="control-label col-xs-2">パラメータ1</label>
					<div class="col-xs-8">
						<input id="param1" name="param1" type="text"
							class="form-control" value="${fn:escapeXml(form.param1)}">
						<%-- エラー時メッセージ表示用 --%>
						<c:if test="${!empty errorMap['param1']}">
						<div style="text-align:left"><small class="text-danger"><c:out value="${errorMap['param1']}"/></small></div>
						</c:if>
					</div>
				</div>
			</div>
			<div class="col-xs-10">
				<div class="form-group">
					<label for="param2" class="control-label col-xs-2">パラメータ2</label>
					<div class="col-xs-8">
						<input id="param2" name="param2" type="text"
							class="form-control" value="${fn:escapeXml(form.param2)}">
						<%-- エラー時メッセージ表示用 --%>
						<c:if test="${!empty errorMap['param2']}">
						<div style="text-align:left"><small class="text-danger"><c:out value="${errorMap['param2']}"/></small></div>
						</c:if>
					</div>
				</div>
				<div class="form-group">
					<div class="col-xs-offset-2 col-xs-10" style="text-align:left">
						<button id="formSubmit" name="formSubmit" type="submit" class="btn btn-primary">登録</button>
					</div>
				</div>
			</div>
		</div>
		</form>
```
- ${form} は後ほど説明します Form オブジェクト
- ${errorMap} はエラー内容が入ってくる Map です
- **escapeXml を自前でやる必要あり**
- とてもシンプルにできます。パラメータが増えたら同様に増やすだけ。

### CustomForm クラス
```CustomForm.java
public class CustomForm {

	private Long 		param1;
	private String 		param2;

	private Map<String, Object> errorMap;

	// コンストラクタ
	public CustomForm() {
		errorMap = Collections.synchronizedMap(new MultiValueMap<String, Object>());
	}
	// バリデーション
	public boolean validate() {
		if (param1 == null || param1 < 0) {
			errorMap.put("param1", "パラメータ1が正しくありません。");
		}
		if (StringUtils.isBlank(param2) || StringUtils.length(param2) > 50) {
			errorMap.put("param2", "パラメータ2の長さが1~50文字ではありません。");
		}
		if (errorMap.isEmpty()) {
			return true;
		}
		return false;
	}
	// getter,setter,etc...
}
```
- org.apache.commons.collections4.map.MultiValueMap をエラーマップにしているのでエラー内容を複数設定可能

### inputAction 用 メソッド
```java:CustomFormController.java
	@RequestMapping(value = "/inputAction/", method = RequestMethod.GET)
	public String inputAction(
			Model model, HttpServletRequest request) {

		return inputActionInternal(null, model, request);
	}
	public String inputActionInternal(CustomForm errorForm,
			Model model, HttpServletRequest request) {
	
		// 何か処理があれば追加…

		// 初回表示用
		if (errorForm == null) {
			CustomForm form = new CustomForm();
			model.addAttribute("form", form);
		}
		else {
			// エラー表示用
			model.addAttribute("form", errorForm);
		}
		return "inputAction";
	}
```
- エラー時の Form オブジェクトを表示するためにメソッドを2つに分けています

### nextAction 用 メソッド
```java:CustomFormController.java
	@RequestMapping(value = "/nextAction/", method = {RequestMethod.GET,RequestMethod.POST})
	public Object nextAction(@ModelAttribute CustomForm form,
			Model model, HttpServletRequest request, HttpServletResponse response, RedirectAttributes attributes) {

		// バリデーション
		if (!form.validate()) {
			// 入力エラー
			return inputActionInternal(form, model, request);
		}

		// データ登録など…

		// 完了画面へリダイレクト
		RedirectView redirect = new RedirectView("/finishAction/");
		attributes.addFlashAttribute("form", form);
		redirect.setContextRelative(true);
		redirect.setExposeModelAttributes(false);
		return redirect;
	}
```

完了画面は表示するだけなので省略します。
&lt;form:form&gt; 形式じゃなくても割と使えます。
以上、お疲れさまでした！