目次
Spring Bootでの実用的な機能をわかりやすく解説中
Spring Boot のテンプレートエンジンとしては、Thymeleaf が有名です。本ページでは、フォーム関連の処理について、基本的なサンプルコードをまとめます。Rails におけるビューヘルパーや、フォーム入力値のバリデーションに相当する機能です。
公式ドキュメント
サンプルプロジェクト構成
.
|-- build.gradle
|-- gradle
| `-- wrapper
| |-- gradle-wrapper.jar
| `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
`-- src
`-- main
|-- java
| `-- hello
| |-- Application.java
| |-- Greeting.java
| `-- GreetingController.java
`-- resources
`-- templates
|-- greeting.html
`-- result.html
Java ソースコード
Greeting.java
コントローラとビューで値のやり取りを行うための、入れ物となるクラスです。id
や content
は HTML ファイル内の form 内の input に対応しています。
package hello;
public class Greeting {
private long id;
private String content;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
GreetingController.java
@RequestMapping(path = "/greeting", method = RequestMethod.GET)
に相当するアノテーション @GetMapping("/greeting")
を利用しています。@GetMapping
側のアクションについては、Model model
に空の Greeting オブジェクトを格納して greeting ビューで利用しています。@PostMapping
側のアクションについては、@ModelAttribute
が付与された Greeting greeting
に POST されたパラメータが自動で格納されており、result ビューで利用されます。
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class GreetingController {
@GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
return "greeting";
}
@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting) {
// return "greeting"; // 同じビューを使い回すこともできます。
return "result";
}
}
HTML ファイル
greeting.html
@{}
を利用してリンクすることで、例えばアプリケーション全体が /myapp
といったプレフィックス以下で動作する場合にも対応できます。また、th:field
はモデルのメンバ変数と input をバインドするための設定です。
Relative URLs starting with / (eg: /order/details) will be automatically prefixed by the application context name.
http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
</body>
</html>
result.html
POST された値を Thymeleaf の記法にしたがって設定して表示します。ここまで内容は http://localhost:8080/greeting にアクセスして動作確認できます。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<a href="/greeting">Submit another message</a>
</body>
</html>
チェックボックス
チェックボックスが一つの場合
input タグには id が自動で付与されて、th:for
から参照されます。
greeting.html
<div>
<label th:for="${#ids.next('admin')}">Admin</label>
<input type="checkbox" th:field="*{admin}" />
</div>
Greeting.java
private boolean isAdmin;
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean isAdmin) {
this.isAdmin = isAdmin;
}
チェックボックスが複数の場合
先程の例の #ids.next()
ではなく #ids.prev()
を用いると、label タグを input タグの後ろに設置できます。また、先程の例の isAdmin と異なり、今回の features は boolean ではないため、複数チェックボックスに対してそれぞれ th:value
で値を設定しています。
greeting.html
<ul>
<li th:each="feat : ${allFeatures}">
<input type="checkbox" th:field="*{features}" th:value="${feat}" />
<label th:for="${#ids.prev('features')}"
th:text="${feat}">feature label</label>
</li>
</ul>
Greeting.java
private List<String> features;
public List<String> getFeatures() {
return features;
}
public void setFeatures(List<String> features) {
this.features = features;
}
GreetingController.java
@GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
model.addAttribute("allFeatures", Arrays.asList("xxx", "yyy"));
return "greeting";
}
@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting, Model model) {
greeting.setId(greeting.getId() + 1);
model.addAttribute("allFeatures", Arrays.asList("xxx", "yyy"));
return "greeting";
}
ラジオボタン
ラジオボタンは、複数チェックボックスの場合と似た記法になります。ただし、モデルが保持する値は単数です。
greeting.html
<ul>
<li th:each="feat : ${allFeatures}">
<input type="radio" th:field="*{feature}" th:value="${feat}" />
<label th:for="${#ids.prev('feature')}" th:text="${feat}">feature label</label>
</li>
</ul>
Greeting.java
private String feature;
public String getFeature() {
return feature;
}
public void setFeature(String feature) {
this.feature = feature;
}
GreetingController.java
(複数チェックボックスの場合と同じであるため、省略。)
セレクトボックス
セレクトボックスはラジオボタンとほぼ同じ構造になります。以下の例において、Greeting.java と GreetingController.java はラジオボタンと同じであるため省略します。
greeting.html
<select th:field="*{feature}">
<option th:each="feat : ${allFeatures}"
th:value="${feat}"
th:text="${feat}"></option>
</select>
基本的なフォームバリデーション
Validating Form Input に記載の org.hibernate:hibernate-validator
は、org.springframework.boot:spring-boot-starter-thymeleaf
が依存するため build.gradle
に明記する必要はありません。依存関係は以下のコマンドで確認できます。
./gradlew dependencies
Greeting.java
バリデーションは Greeting.java で設定します。
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
上記ライブラリをインポートして、メンバ変数にアノテーションを設定します。@NotNull
で null を許容しない設定になります。@Max
による最大値設定や、@Size
による文字列長の設定も可能です。
@NotNull
@Max(10)
private long id;
@NotNull
@Size(min=2, max=30)
private String content;
GreetingController.java
@ModelAttribute
ではなく @Valid
を設定することで、バリデーションが行われます。バリデーション結果 bindingResult
によって、表示するビューを変更します。エラーがある場合は、エラーメッセージと伴に、再度フォームビューを表示します。その際、フォームにはユーザーが入力した値が設定された状態になります。
package hello;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class GreetingController {
@GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
return "greeting";
}
@PostMapping("/greeting")
public String greetingSubmit(@Valid Greeting greeting, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "greeting";
}
return "result";
// return "redirect:/somewhere"; // リダイレクトさせることもできます。
}
}
greeting.html
エラーが存在する場合のみ表示される、th:errors
を用いた以下の二行を追記します。複数のエラーが存在する場合は <br />
で区切られます。
<p th:if="${#fields.hasErrors('id')}" th:errors="*{id}">Id Error</p>
<p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</p>
th:each
で個別にタグを設定することもできます。
<p th:each="err : ${#fields.errors('id')}" th:text="${err}"></p>
<p th:each="err : ${#fields.errors('content')}" th:text="${err}"></p>
全体としては以下のようになります。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p th:if="${#fields.hasErrors('id')}" th:errors="*{id}">Id Error</p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
</body>
</html>
エラー時の CSS 設定
th:field
が設定された input タグに対して、エラー時に特定の CSS クラスを設定することができます。th:errorclass
を利用します。上書きではなく、クラスが追加されます。
<p>Id: <input type="text" th:field="*{id}" th:errorclass="has-error" /></p>
フォーム全体のエラー
input タグ毎ではなく、フォーム全体としてエラーを表示したい場合は *
または all
でエラー処理します。その際、form タグ内でしか利用できないことに注意します。
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
</ul>
意味内容としては同じですが、専用の関数 hasAnyErrors()
と allErrors()
を利用して書き換えると以下のようになります。
<ul th:if="${#fields.hasAnyErrors()}">
<li th:each="err : ${#fields.allErrors()}" th:text="${err}"></li>
</ul>
<br />
区切りで表示したい場合は以下のようにします。
<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}"></p>
オブジェクトとしてより詳細に制御したい場合は以下のようにします。やはり form タグ内でしか利用できないことに注意します。
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:unless="${e.global}">
<span th:text="${e.fieldName}"></span> | <span th:text="${e.message}"></span>
</li>
</ul>
フォームタグの外でエラーを表示
<ul th:if="${#fields.hasErrors('${greeting.*}')}">
<li th:each="err : ${#fields.errors('${greeting.*}')}" th:text="${err}"></li>
</ul>
<form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
...
エラーメッセージの日本語化
messages_{en|ja}.properties
と同様に、ValidationMessages_{en|ja}.properties
を利用したバリデーションメッセージの多言語対応ができます。以下のページの項目に対応する設定を行うことで、エラーメッセージをカスタマイズできます。
ただし、Spring Boot のエンコーディング設定の関係で、日本語は文字化けしてしまいます。簡単な対応方法としては、native2asciiで ascii コードに変換することです。また、Eclipse を利用している場合、マーケットプレイスの Properties Editor をインストールすれば、閲覧編集が容易になります。
native2ascii -encoding UTF-8 src/main/resources/ValidationMessages_ja.properties src/main/resources/ValidationMessages_ja.properties
例えば以下のようになります。
src/main/resources/ValidationMessages_ja.properties
javax.validation.constraints.Size.message = {min} \u6587\u5b57\u4ee5\u4e0a {max} \u6587\u5b57\u4ee5\u4e0b\u3067\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
src/main/resources/ValidationMessages_en.properties
javax.validation.constraints.Size.message = size must be between {min} and {max}
リダイレクト先にパラメータを引き継ぐ
コントローラで以下のように文字列を返すことで、302 リダイレクトを発生させることができます。
return "redirect:/somewhere";
その際、リダイレクト先にパラメータを渡すためには RedirectAttributes を利用します。Rails における flash / notice / alert に相当する機能です。
GreetingController.java
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class GreetingController {
@GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
return "greeting";
}
@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting, RedirectAttributes redirectAttributes) {
// 直接格納
redirectAttributes.addFlashAttribute("id", greeting.getId());
// 入れ物 modelMap を用意して格納
ModelMap modelMap = new ModelMap();
modelMap.addAttribute("content", greeting.getContent());
redirectAttributes.addFlashAttribute("modelMap", modelMap);
return "redirect:/somewhere";
}
@GetMapping("/somewhere")
public String someWhere(@ModelAttribute("id") long id, @ModelAttribute("modelMap") ModelMap modelMap) {
System.out.println(id);
System.out.println(modelMap.get("content"));
return "redirect:/greeting";
}
}
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...