Spark Frameworkの使い方:Getting Start編 はこちら
Spark Web Framework の使い方 : RESTアプリを作成する はこちら
今回はSpark Frameworkを使ったページ(画面)アプリの作成について解説します。
今回使用するライブラリ
- spark-core 2.7.1
- spark-template-thymeleaf 2.7.1
- spark-debug-tools 0.5
spark-template-thymeleafを依存関係に追加します。
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-thymeleaf</artifactId>
<version>2.7.1</version>
</dependency>
Spark Frameworkにおけるページアプリ
RESTアプリを作成するでやったように、Spark FrameworkではHTTP Methodのget
・post
・put
・delete
に対応するマッピングメソッドが用意されています。このうち、ページアプリではget
・post
を利用します。
get("/", (req, res) -> { ... });
post("/", (req, res) -> { ... });
また、各マッピングメソッドには、レスポンスの内容によって異なるRoute
インターフェイスが用意されています。このうち、ページアプリではTemplateViewRoute
インターフェイスを利用します。
get(String, TemplateViewRoute, TemplateEngine);
TemplateViewRoute
インターフェイスのシグネチャを見てみましょう。
@FunctionalInterface
public interface TemplateViewRoute {
ModelAndView handle(Request request, Response response) throws Exception;
}
TemplateViewRoute
インターフェイスはRoute
と同様に@FunctionalInterface
で定義されており、リクエストとレスポンスを引数にとり、ModelAndView
オブジェクトを返却するよう実装します。
ModelAndView
は、テンプレートとなるView名と、テンプレートで使用する変数(Model)を格納するオブジェクトです。
TemplateEngine
マッピングメソッドの第3引数となるTemplateEngine
は、実際のテンプレートエンジンを呼び出すためのラッパークラスで、Sparkの付属ライブラリとして以下が提供されています。
- spark-template-closure
- spark-template-freemarker
- spark-template-handlebars
- spark-template-jade
- spark-template-jetbrick
- spark-template-jinjava
- spark-template-jtwig
- spark-template-mustache
- spark-template-mvel
- spark-template-pebble
- spark-template-rocker
- spark-template-thymeleaf
- spark-template-velocity
- spark-template-water
今回は、テンプレートエンジンとしてThymeleaf(spark-template-thymeleaf)を利用することにします。
ページアプリを実装する
前提となる実装
今回はRESTアプリを実装するで作成したTODOリストを管理するRESTアプリを、ページアプリに変換します。
大きな違いとして、前述のTemplateViewRote
を利用するのに加え、RESTアプリとページアプリではURL(パス)の体系が異なります。
- RESTアプリ
findAll : get /todos
findOne : get /todos/:id
create : post /todos
finish : put /todos/:id
remove : delete /todos/:id
- ページアプリ
findAll : get /todos
findOne : -
create : post /todos/create
finish : post /todos/finish
remove : post /todos/remove
実装
それではページアプリを実装していきます。
まずはWebページのテンプレートファイルを作成します。
spark-template-thymeleaf
のThymeleafTemplateEngine
のデフォルトでは、テンプレートファイルはsrc/main/resources/templates/*.html
というファイル体系になります。
- todos.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/styles.css"><!-- CSSファイルを読み込む -->
<title>Todo</title>
</head>
<body>
<h1>Todo List</h1>
<!-- TODOを新規登録するHTMLフォーム -->
<form method="post" action="/todos/create">
<input name="title">
<button>create</button>
</form>
<hr>
<!-- 登録されたTODOを表示するテーブル -->
<table th:if="${not #lists.isEmpty(todos)}">
<thead>
<tr>
<th>title</th>
<th>created</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="todo : ${todos}" th:object="${todo}">
<td th:text="*{title}"></td>
<td th:text="*{#temporals.formatISO(created)}"></td> <!-- (1) -->
<td th:switch="*{finished}">
<!-- 登録されたTODOを完了するHTMLフォーム -->
<span th:case="true">finished</span>
<form th:case="*" method="post" action="/todos/finish">
<button name="id" th:value="*{id}">finish</button>
</form>
</td>
<td>
<!-- 登録されたTODOを削除するHTMLフォーム -->
<form method="post" action="/todos/remove">
<button name="id" th:value="*{id}">remove</button>
</form>
</td>
</tr>
</tbody>
</table>
</body>
</html>
no. | description |
---|---|
(1) |
#temporals#formatISO メソッドでJava 8 Date and Time APIの日付オブジェクトをフォーマットしています。spark-template-thymeleaf ではデフォルトでthymeleaf-extras-java8time をサポートしており、#temporals を利用することができます。 |
ここでは
action
属性の指定方法としてリンクURL式(@{}
)を利用していませんが、spark-template-thymeleaf
ではリンクURL式をサポートしていないためです。Thymeleafの機能をすべてサポートしているわけではないので注意する必要がありそうです。
次にコントローラクラスを作成します。
前回と同様、ハンドラの処理が複雑化することを考慮して、ハンドラをApplication.javaから分離してコントローラクラスを作成します。
- TodoController.java
public class TodoController {
private static final TodoRepository repository = new TodoRepository();
// (1)
public static final TemplateViewRoute findAll = (req, res) -> {
Map<String, Object> model = new HashMap<>(); // (2)
model.put("todos", repository.findAll()); // (3)
return new ModelAndView(model, "todos"); // (4)
};
// (5)
public static final Route create = (req, res) -> {
repository.create(req.queryParams("title"); // (6)
res.redirect("/todos"); // (7)
return null; // (8)
};
public static final Route finish = (req, res) -> {
repository.finish(req.queryParams("id"));
res.redirect("/todos");
return null;
};
public static final Route remove = (req, res) -> {
repository.remove(req.queryParams("id"));
res.redirect("/todos");
return null;
};
}
no. | description |
---|---|
(1) | 画面を返却するハンドラはTemplateViewRoute インターフェイスを実装します。@FunctionalInterface なので分かりづらいかもしれませんが、匿名クラスのオブジェクトを生成してクラスフィールドに格納しています。 |
(2) | 戻り値のModelAndView オブジェクトに指定するMap<String, Object> オブジェクト(Model)を生成します。 |
(3) | Modelに変数を追加します。ここでは"todos"という名前で、リポジトリから取得したTODOリストをセットしています。 |
(4) | 戻り値として、前述のModelとView名(テンプレートファイルの名前)をセットしたModelAndView オブジェクトを返却します。テンプレートファイルの解決とHTMLの生成は前述のとおりTemplateEngine が実施します。 |
(5) | create以降のパスでは、処理を行った後findAllのパスにリダイレクトします。リダイレクトするパスでは画面を返却しないため、TemplateViewRoute ではなくRoute インターフェイスを実装します。 |
(6) |
Request#queryParams メソッドでHTMLフォームで入力されたtitle を取得できます。 |
(7) |
Response#redirect メソッドでリダイレクトすることができます。なお、 Response を取れない状況ならSpark.redirect メソッドを利用してリダイレクトすることも可能です。 |
(8) | リダイレクトしてもメソッドは終了しないので、null を返却します。ちなみにTemplateViewRoute を実装した場合は、無駄にModelAndView オブジェクトを生成して返却する必要があります。 |
リダイレクト後のロジックをスキップする手段として、リクエストの処理を中断する
Spark.halt
メソッドがありますが、Spark.after
メソッドで適用したフィルタもスキップしてしまうので、利用には注意が必要そうです。
これで必要なピースは揃ったので、あとはリクエストマッピングの設定を行うだけです。
- Application.java
public class Application {
private static final TemplateEngine templateEngine = new ThymeleafTemplateEngine();
public static void main(String[] args) {
// configure application.
staticFiles.location("/public"); // (1)
staticFiles.expireTime(600L);
enableDebugScreen();
// configure routes.
path("/todos", () -> { // (2)
// (3)
get("", TodoController.findAll, templateEngine);
// (4)
post("/create", TodoController.create);
post("/finish", TodoController.finish);
post("/remove", TodoController.remove);
});
}
}
no. | description |
---|---|
(1) | 静的リソースをホストするため、staticFiles メソッドが用意されています。ここでは、src/main/resources/public ディレクトリのCSSファイルを公開し、ブラウザキャッシュを10分間保持させています。 |
(2) |
path メソッドは、パスの階層化・グルーピングに使用します。例えば、2番目のpost メソッドにはパス/create を指定しているので、/todos/create というパスへのリクエストがマッピングされることになります。 |
(3) | 画面を返却するメソッドとパスに対するリクエストを処理するハンドラに先ほど作成したコントローラクラスを指定し、テンプレートを処理するTemplateEngine にThymeleafTemplateEngine を指定しています。 |
(4) | リダイレクトするパスでは画面を返却する必要がないため、TemplateEngine は指定しません。 |
実行
アプリを起動して、ブラウザからhttp://localhost:4567/todos
にアクセスすると、TODOリストの画面が返却され、ページアプリとして機能していることが分かると思います。
まとめ
TemplateViewRoute
とテンプレートエンジンを利用して、ページアプリをシンプルに実装できることが分かりました。
REST APIに比べると、HTMLフォームのパラメータからモデルに変換する機構がないため、ここをどう実装するかがポイントになりそうですね。
次回以降、ロギングや入力チェックなど、アプリを作成する上でポイントとなる機能の実装について解説していきます。