ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

Spring Bootで作るWebアプリケーション③ - 画面遷移のあるWebアプリケーションの作成

前回までで、Spring Bootを使ったWebアプリケーションのGradleプロジェクトの作成をしました。

今回からは、いよいよSpring BootをつかってWebアプリケーションを構築していきます。

1. 実装する画面のイメージ

まずは手始めに3画面のWebアプリケーションを作成してみます。

初めの画面で名前を入力、次の画面で年齢を入力、最後の画面で入力内容を表示します。
画面遷移のイメージは次のようになります。

f:id:penguinlabo:20200223170254p:plain

2. 画面作成の準備

画面はテンプレートエンジンのThymeleafを使用します。ここではThymeleafの説明は省略しますが、昔は標準だったJSPのようなものと思えばよいです。

Thymeleafを使用するため、プロジェクトの依存関係にThymeleafを追加する必要があります。
前回、編集したbuild.gradleファイルのdependenciesに依存関係を追加します。Spring Bootで用意されている「spring-boot-starter-thymeleaf」を依存関係に追加しましょう。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

build.gradleを更新したら、更新内容をプロジェクトに反映する必要があります。プロジェクトのトップ、もしくはbuild.gradleの上で右クリックして、[Gradle] - [Gradleプロジェクトのリフレッシュ]をクリックします。
この操作は、今後、何度も出てくるので覚えておきましょう。

3. 画面の作成

Thymeleafは画面をHTMLで作成するので、HTMLを書くスキルさえあれば画面を作成できます。

テンプレートファイルはプロジェクトの「src/main/resources/templates」に配置します。
名前入力画面は次のような実装になります。「src/main/resources/templates」に「name.html」として作成します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Name</title>
</head>
<body>
<form th:action="@{/profile/age}" method="post">
    <p>
        <label for="name">名前を入力してください</label>
        <input type="text" name="name" id="name">
    </p>
    <p>
        <input type="submit" value="次へ">
    </p>
</form>
</body>
</html>

どうでしょう。ほとんどHTMLとの違いを感じないと思います。実際、このファイルをブラウザで直接開いても、レイアウトが崩れることなく表示されます。JSPでは特殊なタグを使用するので、ブラウザでレイアウトを確認できませんでしたが、Thymeleafだとレイアウトを確認しながら作成することができます。

では、通常のHTMLと違う部分を解説します。

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

htmlタグに「xmlns:th」属性の指定があります。通常のHTMLではない属性です。これはThymeleaf独自の属性を使用するための準備で、「th」という名前を定義しています。

<form th:action="@{/profile/age}" method="post">

formの属性にも違いがあります。通常は入力値をサーバに送信するときのURLを定義する「action」属性を指定しますが、「th:action」属性になっています。
通常のaction属性はURLのホスト名以降のパスを指定します。例えば、フォームの送信先URLが「hhtp://localhost/penguin-web/profile/age」の場合、「/penguin-web/profile/age」を指定します。
しかし、通常のWebアプリケーションではURLのパスの第1要素にあたるコンテキストルートは、実装中に含めないようにすべきです。コンテキストルートは設定で簡単に切り替えられるので、実装中にコンテキストルートを記述してしまうと、コンテキストルートの設定を変更すると、実装側も修正する必要が出てくるためです。
Thymeleafの属性である「th:action」属性に「@{コンテキストルートを除いたパス}」を指定することで、実行時にコンテキストルートが自動で追加されます。

年齢入力画面は次のような実装になります。「src/main/resources/templates」に「age.html」として作成します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Age</title>
</head>
<body>
<form th:action="@{/profile/hello}" method="post">
    <p>
        <label for="age"><span th:text="${name}">あなた</span>さんの年齢を入力してください</label>
        <input type="text" name="age" id="age">
    </p>
    <p>
        <input type="submit" value="次へ">
    </p>
</form>
</body>
</html>

名前入力画面の実装とほとんど同じですが、一か所違いがあります。
最初の画面イメージにあるとおり、年齢入力画面には、一つ前の名前入力画面で入力された名前を表示します。
実装している箇所は次の部分です。

        <label for="age"><span th:text="${name}">あなた</span>さんの年齢を入力してください</label>

これをブラウザで表示すると、「あなたさんの年齢を入力してください」と表示されます。
ポイントは、「あなた」を括っているspan要素の「th:text」属性です。「th:text」属性の値として、「${name}」が指定されています。これは、span要素のテキストをサーバサイドで設定した「name」の値に置き換えることを意味します。
サーバの実装で説明しますが、この実装によって、span属性のテキスト「あなた」部分が名前入力画面で入力された値に置き換えられます。

最後に、ようこそ画面は次のような実装になります。「src/main/resources/templates」に「hello.html」として作成します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
    <p>ようこそ<span th:text="${name}">あなた</span>さん</p>
    <p><span th:text="${age}">20</span>歳なんですね。</p>
</body>
</html>

年齢入力画面と同様、名前と年齢を表示する部分にspan要素を配置し、「th:text」属性で、サーバサイドで設定した名前と年齢が表示されるようにしています。

4. コントローラークラスの作成

画面の作成が終わったので、次にWebアプリケーションの処理部分を作成します。

Spring Frameworkでは、画面にまつわる処理はコントローラーと呼ばれるクラスで実装します。
今回のコントローラーは以下のようになります。細かな説明は後程します。
src/main/javaに「penguin.web.controller」パッケージを作成して、「ProfileController」クラスを作成します。

package penguin.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("profile")
public class ProfileController {
    @RequestMapping("name")
    public String name() {
        return "name";
    }

    @RequestMapping(path = "age", method = RequestMethod.POST)
    public String age(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "age";
    }

    @RequestMapping(path = "hello", method = RequestMethod.POST)
    public String hello(@RequestParam("age") String age, Model model) {
        model.addAttribute("age", age);
        return "hello";
    }
}

クラス定義部

@Controller
@RequestMapping("profile")
public class ProfileController {

クラスに2つのアノテーションが指定されています。

1つめは「@Controller」アノテーションです。このクラスがコントローラークラスであることをSpring Frameworkに伝えるためのもので、「@Controller」アノテーションの指定は必須です。

2つめは「@RequestMapping」アノテーションです。属性に「profile」という文字列が指定されています。Spring FrameworkのWebアプリケーションは、URLのパス部分をコントローラーで指定します。ここで「@RequestMapping」アノテーションに指定した「profile」はURLの一部になります。URLの全体がどうなるかは後で説明します。「@RequestMapping」アノテーションの指定は必須ではありません。

メソッド定義部

    @RequestMapping("name")
    public String name() {
        return "name";
    }

まずは、一つ目の「name」メソッドです。
このメソッドの役割は名前入力画面を表示することです。

メソッドに「@RequestMapping」アノテーションが指定されています。属性に「name」が指定されています。これはクラスに指定されていた「@RequestMapping」と同様、URLの一部になります。
そして、「name」という文字列を復帰値としています。コントローラクラスの復帰値はいろんな意味がありますが、ここではThymeleafのテンプレートファイルのファイル名を表します。テンプレートファイルは「src/main/resources/templates」フォルダに作成したことを思い出してください。どのテンプレートファイルがブラウザに表示されるかは、以下の内容で決定されます。

"src/main/resources/templates/" + コントローラーメソッドの復帰値 + ".html"

このメソッドは「name」を復帰値としているので、ブラウザには「src/main/resources/templates/name.html」の内容が表示されることになります。

    @RequestMapping(path = "age", method = RequestMethod.POST)
    public String age(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "age";
    }

次は、二つ目の「age」メソッドです。
このメソッドの役割は名前入力画面で入力された名前を受け取り、年齢入力画面を表示することです。

@RequestMappingに2つの属性が指定されています。1つ目のpath属性は属性名を省略したときと同じ意味で、URLの一部を指定する属性です。
2つ目のmethod属性は受け付けるHTTPメソッドになります。ここでは「RequestMethod.POST」を指定しているので、POSTメソッドのリクエストのみ受け付けることになります。

メソッドには2つの引数があります。第1引数には@RequestParamアノテーションが指定されています。そしてアノテーションの属性に「name」とあります。この@RequestParamアノテーションは、画面から送信されたフォームデータを受け取るためのもので、受け取るフォームデータの名前を属性に指定します。
名前入力画面の以下の箇所で入力された値が、この引数に設定されることになります。

        <input type="text" name="name" id="name">

input要素のname属性の値と、コントローラーメソッドの引数の@RequestParamアノテーションの属性を一致させることで、入力内容を受け取れます。
第2引数のModelは、次の画面に渡すデータを設定するためのオブジェクトです。メソッド内の実装でModelオブジェクトが使用されています。

        model.addAttribute("name", name);

Modelオブジェクトに「name」という名前で、名前入力画面で入力された値を追加しています。これにより、遷移先の年齢入力画面で「name」という名前を使って、名前入力画面で入力された値を参照することができます。
名前入力画面の次の一文が、この「name」の値を参照している部分です。

        <label for="age"><span th:text="${name}">あなた</span>さんの年齢を入力してください</label>

span要素の「th:text」属性に指定している「${name}」が、Modelに設定した「name」を参照していることになります。

そして、最後の「hello」メソッドです。
このメソッドの役割は年齢入力画面で入力された年齢を受け取り、ようこそ画面を表示することです。

    @RequestMapping(path = "hello", method = RequestMethod.POST)
    public String hello(@RequestParam("age") String age, Model model) {
        model.addAttribute("age", age);
        return "hello";
    }

実装内容は、ageメソッドと同等です。

5. ここまでのまとめ

一気に実装を説明したので、いったん、ここまでのコントローラークラスの実装と画面の関係をまとめます。
名前入力画面と年齢入力画面の関係は次のようになります。

f:id:penguinlabo:20200223225243p:plain

6. Spring Bootアプリケーションの設定

ここまでで、Webアプリケーションとしての実装は完了ですが、Spring Bootアプリケーションにするための設定が必要です。
あと少しです。頑張りましょう!!

Spring Boot Applicationクラスの作成

Spring Bootのアプリケーションとして認識されるために、@SpringBootApplicationアノテーションを指定したクラスと、アプリケーションの起動ポイントとなるmainメソッドが必要となります。
penguin.webパッケージに以下の内容の「Application」クラスを作成します。

package penguin.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

コンテキストルートの設定

Spring Bootで作るWebアプリケーション② - Gradleプロジェクトの編集 - ぺんぎんらぼで、Webアプリケーションのコンテキストルートを設定しましたが、実は、この設定はSpring BootのWebアプリケーションでは有効になりません。
Spring Bootでは、application.propertiesファイルにコンテキストルートの設定を記述する必要があります。

src/main/resourcesにプロパティファイル「application.properties」を作成して、以下の内容を記述します。

server.servlet.context-path=/penguin-web
7. さあ、いよいよ実行です!!

おつかれさまでした!! これで、やっとSpring BootのWebアプリケーションとして実行するための実装が完了です。
いよいよ実行ですが、その前に、プロジェクトの構成が以下の通りになっているか、最終確認しましょう。

f:id:penguinlabo:20200223232759p:plain

今回、作成したファイルは以下のものです。

src/main/java

格納先 ファイル名 内容
penguin.web Applicationクラス Spring Boot Applicationクラス
penguin.web.controller ProfileControllerクラス 今回作成した画面のコントローラークラス

src/main/resources

格納先 ファイル名 内容
/templates name.html 名前入力画面
/templates age.html 年齢入力画面
/templates hello.html ようこそ画面
/ application.properties Spring Boot設定ファイル

問題がなければWebアプリケーションを実行します。実行方法は簡単で、作成したApplicationクラスのmainメソッドを実行するだけです。

f:id:penguinlabo:20200223234410p:plain

実行すると、コンソールに次のような起動ログが表示されます。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-02-23 23:42:04.846  INFO 832 --- [           main] penguin.web.Application                  : Starting Application on DESKTOP-I4DAKV1 with PID 832 (C:\workspace\penguin-web\bin\main started by Administrator in C:\workspace\penguin-web)
2020-02-23 23:42:04.862  INFO 832 --- [           main] penguin.web.Application                  : No active profile set, falling back to default profiles: default
2020-02-23 23:42:08.518  INFO 832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-02-23 23:42:08.543  INFO 832 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-23 23:42:08.559  INFO 832 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.30]
2020-02-23 23:42:08.856  INFO 832 --- [           main] o.a.c.c.C.[.[localhost].[/penguin-web]   : Initializing Spring embedded WebApplicationContext
2020-02-23 23:42:08.856  INFO 832 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3807 ms
2020-02-23 23:42:09.465  INFO 832 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-23 23:42:10.293  INFO 832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '/penguin-web'
2020-02-23 23:42:10.302  INFO 832 --- [           main] penguin.web.Application                  : Started Application in 6.887 seconds (JVM running for 9.477)

アプリケーションが起動したので、Webブラウザからアクセスしてみます。アクセスするURLは以下です。

http://localhost:8080/penguin-web/profile/name

このURLは、application.propertiesに設定したコンテキストルートと、コントローラークラスの@RequestMappingアノテーションで指定した値から決定されます。

f:id:penguinlabo:20200224002655p:plain

アクセスすると、名前入力画面が表示されるので、入力欄に「田中一郎」と入力します。

f:id:penguinlabo:20200224003458p:plain

名前入力画面で「次へ」ボタンをクリックと、年齢入力画面が表示されるので、入力欄に「25」と入力します。

f:id:penguinlabo:20200224003621p:plain

年齢入力画面で「次へ」ボタンをクリックと、ようこそ画面が表示されます。

f:id:penguinlabo:20200224003719p:plain

ようこそ画面をよく見てください。入力した年齢は正しく「25歳なんですね。」と表示されています。
しかし、入力した名前は表示されず「ようこそさん」と表示されています。「ようこそ田中一郎さん」と表示されることを期待した実装です。

なぜ、ようこそ画面に名前が表示されなかったのか。どのようにすれば、ようこそ画面に名前が表示されるかは、次回、説明します。

8. 次回予告

いよいよ、Webアプリの実装が始まりました。
今回は初回であることもあり、かなり詳しく説明したので、かなりのボリュームになりました。
皆さんは実装してみてどのように感じたでしょう?「意外と簡単」と感じた人もいれば、「思ってたより、いろいろと実装しないといけない」と感じた人もいるでしょう。ただ、実装した内容をしっかりと理解すると「意外と簡単!!」と思えるものです。まずは実装して動かす、そして内容を理解することが大切です。

では、次回の「Spring Bootで作るWebアプリケーション④ - 画面間のデータの受け渡し」でお会いしましょう!!