Spring Webでセッションを使って数当てゲームを作成します。
プロジェクトの作成
Intellij IDEAでSpring Initializrを選択しプロジェクトを作成します。
依存関係として、以下を選択します。
- Spring Web
- Spring Boot DevTools
- Thymeleaf
- Lombok
Controllerクラスの作成
package com.example.bigorsmallspring;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Controller
public class GameController {
@Autowired
HttpSession session;
@GetMapping("/")
public String index() {
session.invalidate();
Random rnd = new Random();
int answer = rnd.nextInt(100) + 1;
session.setAttribute("answer", answer);
System.out.println("answer=" + answer);
return "game";
}
@PostMapping("/challenge")
public ModelAndView challenge(@RequestParam("number") int number,
ModelAndView mv) {
int answer = (Integer)session.getAttribute("answer");
@SuppressWarnings("unchecked")
List<History> histories = (List<History>)session.getAttribute("histories");
if (histories == null) {
histories = new ArrayList();
session.setAttribute("histories", histories);
}
if (answer < number) {
histories.add(new History(histories.size() + 1, number, "Too large"));
} else if (answer > number) {
histories.add(new History(histories.size() + 1, number, "Too small"));
} else {
histories.add(new History(histories.size() + 1, number, "Correct!"));
}
mv.setViewName("game");
mv.addObject("histories", histories);
return mv;
}
}
@Autowired
アノテーションは、セッションオブジェクトであるsession
を初期化する役割があります。通常はHttpSessionオブジェクト
はHttpServletRequest(jakarta.servlet.http.HttpServletRequest)
オブジェクトからgetSession()
メソッドで取得します。@Autowired
を付与することで、コントローラークラス起動時にセッションの取得処理が自動的に行われ、sessionオブジェクトに格納されます。
History Class
ユーザーが試行した回答結果を記録しておくためのクラスです。
package com.example.bigorsmallspring;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class History {
private int seq;
private int yourAnswer;
private String result;
}
アノテーション@AllArgsConstructor
を付記することで、以下のようなコンストラクタを生成します。このコンストラクタは実際のコードは生成されず暗黙に存在します。
public History(int seq, int yourAnswer, String result) {
super();
this.seq = seq;
this.yourAnswer = yourAnswer;
this.result = result;
}
アノテーション@Getter
は全フィールドに対するゲッターメソッドを暗黙に生成します。
テンプレート
Thymeleafテンプレートを作成します。src/main/resources/templates/game.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Big or Small</title>
<style>
th, td {
border: solid 1px;
}
table{
border-collapse: collapse;
}
</style>
</head>
<body><h1>Big or Small</h1><form action="/challenge"method="post">
<p><input type="text" name="number" placeholder="Input from 1 to 100"></p>
<p><input type="submit"value="Answer"></p></form>
<hr>
<table><tr><th>Num</th><th>Your answer</th><th>Result</th></tr>
<tr th:each="h:${histories}">
<td th:text="${h.seq}"></td>
<td th:text="${h.yourAnswer}"></td>
<td th:text="${h.result}"></td></tr></table>
<p><a href="/">Start Over</a></p>
</body>
</html>
ポイントは以下の th:each
です。
<tr th:each="h:${histories}">
コレクションオブジェクトであるhistories
を受け取り、要素の数分繰り返して<tr>…</tr>
を生成します。各ループごとにhistories
オブジェクトから取り出したオブジェクトをh
に代入し、入れ子となっている<td>...</td>
ではh
オブジェクトのプロパティにアクセスしています。
このhistories
オブジェクトはController
のchallenge
メソッド内の最後にある以下の行でModelAndViewにセットされています。
mv.addObject("histories", histories);
th:text
の代わりに[[...]]
というインライン式を使うことが出来ます。th:text
に比べると相対的に記述量が少なくシンプルに書くことが出来ます。
<tr th:each="h:${histories}">
<td [[${h.seq}]]</td>
<td [[${h.yourAnswer}]]</td>
<td [[${h.result}]]</td>
</tr>
実行
ブラウザにアクセスします。
実際にいくつか数字を入れて遊んでみましょう。