サーブレット&JSPの基本|Springでみんなのショッピングを作成する

ServletとJSPで作った「ひとこと広場」を、Spring MVCで作り直そう。Controller、Service、DAO、JSPをつなげて、ログイン・投稿・一覧表示・ログアウトまでの流れを完成させる

これまで、Servlet、JSP、EL式、JSTL、DAO、データベースを使って、Webアプリケーションの基本を学習してきました。

その中で作成した「ひとこと広場」は、ログインしたユーザーがひとことを投稿し、投稿一覧をJSPで表示するWebアプリケーションです。

このアプリケーションでは、ログイン情報をセッションスコープに保存し、投稿一覧やエラーメッセージをリクエストスコープに保存して、JSP側でEL式とJSTLを使って表示していました。

今回は、この「ひとこと広場」をSpring MVCで作り直します。

Springを使うと、これまでサーブレットで行っていたリクエスト処理を、Controllerとして作成します。

また、LoginLogic、PostMessageLogic、GetMessageListLogicのような処理担当クラスは、Serviceとして作成します。

データを格納するUserやShortMessageは、Springでも通常のJavaクラスとして作成します。

投稿データをデータベースから取得したり、データベースへ追加したりする処理は、MessagesDAOが担当します。

ビューには、これまで学習してきたJSP、EL式、JSTLを使います。

つまり、Spring版になっても、これまでのServletとJSPの知識はしっかり活かせます。

大きく変わるのは、リクエストを受け取る部分です。

ServletではLogin、Logout、Mainのようなサーブレットクラスを作成していました。

Springでは、それぞれLoginController、LogoutController、MainControllerとして作成します。

プロジェクト名はspring_Hirobaとします。

ベースパッケージはcom.example.demoとします。

ログイン画面で入力するパスワードは1234とします。

データベースは、これまで作成したHirobaデータベースとMESSAGESテーブルを利用する想定で進めます。

Spring版ひとこと広場で作成するもの

Spring版のひとこと広場では、Model、DAO、Service、Controller、JSPを作成します。

それぞれの役割を整理してから作り始めると、全体の流れが分かりやすくなります。

作成するファイル

種類ファイル名配置場所役割
設定ファイルapplication.propertiessrc/main/resourcesJSPの配置場所と拡張子を設定する
データクラスUser.javacom.example.demo.modelログインユーザー情報を持つ
エンティティクラスShortMessage.javacom.example.demo.modelMESSAGESテーブルの1件分を表す
DAOMessagesDAO.javacom.example.demo.dao投稿の取得と追加を担当する
ServiceLoginService.javacom.example.demo.modelログイン判定を行う
ServicePostMessageService.javacom.example.demo.model投稿処理を行う
ServiceGetMessageListService.javacom.example.demo.model投稿一覧を取得する
ControllerLoginController.javacom.example.demo.controllerログイン処理を担当する
ControllerLogoutController.javacom.example.demo.controllerログアウト処理を担当する
ControllerMainController.javacom.example.demo.controllerメイン画面の表示と投稿処理を担当する
JSPindex.jspsrc/main/webappログイン画面を表示する
JSPloginResult.jspsrc/main/webapp/WEB-INF/jspログイン結果画面を表示する
JSPmain.jspsrc/main/webapp/WEB-INF/jsp投稿フォームと投稿一覧を表示する
JSPlogout.jspsrc/main/webapp/WEB-INF/jspログアウト画面を表示する

ひとこと広場では、ログイン画面だけはsrc/main/webappに配置します。

ログイン結果画面、メイン画面、ログアウト画面は、Controllerからフォワードされる画面なので、src/main/webapp/WEB-INF/jspに配置します。

WEB-INF配下のJSPは、ブラウザから直接アクセスされにくく、Controllerを通して表示する画面に向いています。

図1:Spring版ひとこと広場の全体構成

この図から分かること

Spring版のひとこと広場では、ブラウザからのリクエストをControllerが受け取ります。

Controllerは必要に応じてServiceに処理を依頼します。

Serviceは、ログイン判定、投稿登録、投稿一覧取得などの処理を担当します。

投稿データの取得や追加はMessagesDAOが担当し、HirobaデータベースのMESSAGESテーブルとやり取りします。

JSPは、Controllerから渡された情報をEL式やJSTLで表示します。

ServletとJSPで作成したときのMVCモデルを、Spring MVCのController、Service、Viewに置き換えていることが分かります。

Springプロジェクトを作成する

まず、Springプロジェクトspring_Hirobaを作成します。

すでにspring_Hirobaを作成済みであれば、新しく作り直す必要はありません。

Eclipseでは、Springスターター・プロジェクトを使って作成します。

Springプロジェクトの設定

項目設定
プロジェクト名spring_Hiroba
ベースパッケージcom.example.demo
タイプMaven
Javaバージョン25
使用するデータベースH2 Database
ViewJSP

Springプロジェクトを作成したら、src/main/javaの中にcom.example.demoが作成されます。

その下に、controller、model、daoの3つのパッケージを作成します。

Spring プロジェクトの作成方法

「ファイル」メニュー→「新規」→「Spring スターター・プロジェクト(Spring initialzr)」を選択します。

プロジェクト名(半角英数)を「名前」に入力し、以下の開発環境に関する設定を選択し、「次へ」をクリックします。

-プロジェクト名: spring_Hiroba
- タイプ: Maven
 - Javaバージョン: 25

Spring Bootのバージョンと、「使用頻度高」でプロジェクトに組み込む機能(スタータ)を選択し、「完了」をクリックします。

- Spring Bootバージョン: 4.1.x
※バージョンの後ろにSNAPSHOT、M1、M2、RC1、RC2といった英字がついているものは選択しない。
   - 使用頻度高: H2 Databaseを選択(H2 DatabaseのJDBCドライバを手動で配置しなくても、プロジェクト内で利用できるようになります)

パッケージ・エクスプローラーにSpringプロジェクトが追加されていることを確認します。
クリックして展開すると下図のような構成になっています。

パッケージ構成

src/main/java
└─ com.example.demo
   ├─ controller
   │  ├─ LoginController.java
   │  ├─ LogoutController.java
   │  └─ MainController.java
   ├─ model
   │  ├─ User.java
   │  ├─ ShortMessage.java
   │  ├─ LoginService.java
   │  ├─ PostMessageService.java
   │  └─ GetMessageListService.java
   └─ dao
      └─ MessagesDAO.java

Spring MVCでは、役割ごとにパッケージを分けておくと見通しがよくなります。

controllerパッケージには、リクエストを受け取るControllerを配置します。

modelパッケージには、データを持つクラスとServiceを配置します。

daoパッケージには、データベースアクセスを担当するDAOを配置します。

JSPを使うための設定

SpringプロジェクトでJSPをビューとして使うには、application.propertiesにJSPの場所と拡張子を設定します。

ファイル名: application.properties

Controllerがビュー名を返したとき、Springはこの設定を使ってJSPファイルを探します。

application.propertiesに追加する設定

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

この設定により、Controllerがreturn "main"; を返すと、Springは/WEB-INF/jsp/main.jspを探します。

Controllerがreturn "loginResult"; を返すと、Springは/WEB-INF/jsp/loginResult.jspを探します。

ただし、index.jspはsrc/main/webappに配置し、最初のログイン画面として直接表示できるようにします。

pom.xmlを編集する

次に、spring_studyプロジェクト直下にあるpom.xmlを編集します。

pom.xmlは、Mavenプロジェクトで利用するライブラリや設定を管理するファイルです。

SpringプロジェクトでJSPを使う場合、JSPを実行するために必要なdependencyをdependenciesタグ内に追加します。

編集するときは、pom.xmlを右クリックし、次で開くから汎用コード・エディタを選択します。

開いたエディタで、dependenciesタグの中に、JSP用のdependencyを追加します。

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
   <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
</dependency>
<dependency>
   <groupId>org.glassfish.web</groupId>
    <artifactId>jakarta.servlet.jsp.jstl</artifactId>
</dependency>

pom.xml編集時の注意点

注意点内容
追加場所dependenciesタグの内側に追加する
タグの対応dependencyタグの開始と終了が正しく対応しているか確認する
保存追加後に上書き保存する
エラー確認pom.xmlに赤いエラー表示が出ていないか確認する

pom.xmlの記述を間違えると、プロジェクトが正しくビルドできなくなることがあります。

dependencyを追加したあとは、保存してエラーが出ていないか確認しましょう。

JSPを配置するフォルダを作成する

最後に、JSPファイルを配置するフォルダを作成します。

今回の構成では、フォワードされるJSPをsrc/main/webapp/WEB-INF/jspに配置します。

Springプロジェクトの作成方法によっては、src/main以下にwebappフォルダが自動で作成されていない場合があります。

その場合は、手動で作成します。

フォルダ作成の手順

手順操作
1srcフォルダ内のmainフォルダを右クリックする
2新規からフォルダーを選択する
3フォルダー名にwebappを入力して完了する
4作成したwebappフォルダを右クリックする
5新規からフォルダーを選択する
6フォルダー名にWEB-INFを入力して完了する
7作成したWEB-INFフォルダを右クリックする
8新規からフォルダーを選択する
9フォルダー名にjspを入力して完了する

大文字と小文字に注意して作成します。

WEB-INFは大文字で書きます。

jspは小文字で書きます。

JSP配置用フォルダの構成

JSPファイルの配置

JSP配置場所表示方法
index.jspsrc/main/webappブラウザから直接表示する
loginResult.jspsrc/main/webapp/WEB-INF/jspControllerからフォワードする
main.jspsrc/main/webapp/WEB-INF/jspControllerからフォワードする
logout.jspsrc/main/webapp/WEB-INF/jspControllerからフォワードする

情報を持つクラスを作成する

最初に、Webアプリケーション内で扱うデータを持つクラスを作成します。

ひとこと広場では、ログイン中のユーザー情報を表すUserクラスと、投稿データを表すShortMessageクラスを作成します。

どちらもcom.example.demo.modelパッケージに配置します。

Lombokを使うことで、getterやコンストラクタなどの定型的なコードを短く書けます。

ユーザーに関する情報を持つクラスの例

ファイル名: User.java

package com.example.demo.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private String name;
    private String pass;
}

User.javaは、ログイン画面から入力されたユーザー名とパスワードを持つクラスです。

nameにはユーザー名を保存します。

passにはパスワードを保存します。

@Getterを付けることで、getNameやgetPassがLombokによって自動生成されます。

@NoArgsConstructorを付けることで、引数を持たないコンストラクタが自動生成されます。

@AllArgsConstructorを付けることで、すべてのフィールドを引数にもつコンストラクタが自動生成されます。

ログイン成功時には、このUserインスタンスをloginUserという属性名でセッションスコープに保存します。

投稿データを表すクラスの例

ファイル名: ShortMessage.java

package com.example.demo.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ShortMessage implements Serializable {
    private int id;
    private String userName;
    private String text;

    public ShortMessage(String userName, String text) {
        this.userName = userName;
        this.text = text;
    }
}

ShortMessage.javaは、MESSAGESテーブルの1件分の投稿データを表すクラスです。

idには投稿IDを保存します。

userNameには投稿者名を保存します。

textには投稿本文を保存します。

データベースから投稿一覧を取得するときは、id、userName、textを持つShortMessageインスタンスを作成します。

一方、新しい投稿を登録するときは、まだidが決まっていないため、userNameとtextだけを受け取るコンストラクタを使います。

このように、一部のフィールドだけを受け取るコンストラクタは、Lombokでは自動生成できないため、自分で定義します。

図2:UserとShortMessageが持つ情報

この図から分かること

Userクラスは、ログイン画面で入力されたユーザー名とパスワードを持ちます。

ログイン成功時には、Userインスタンスをセッションスコープに保存します。

ShortMessageクラスは、投稿データを表します。

データベースから取得した投稿は、ShortMessageインスタンスとしてアプリケーション内で扱います。

新しい投稿を登録するときも、投稿者名と投稿本文をShortMessageにまとめてServiceやDAOへ渡します。

DAOを作成する

次に、MESSAGESテーブルを操作するMessagesDAOを作成します。

Spring版のひとこと広場でも、データベースアクセスはDAOにまとめます。

MessagesDAOには、投稿一覧を取得するfindAllメソッドと、新しい投稿を追加するcreateメソッドを用意します。

MessagesDAOの役割

メソッド内容
findAllMESSAGESテーブルから投稿一覧を取得する
createMESSAGESテーブルに新しい投稿を追加する

ここでは、H2 DatabaseにHirobaデータベースがあり、MESSAGESテーブルが作成済みであることを前提にします。

MESSAGESテーブルには、ID、USER_NAME、TEXTの列がある想定です。

MESSAGESテーブルを操作するDAOの例

ファイル名: MessagesDAO.java

package com.example.demo.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.example.demo.model.ShortMessage;

public class MessagesDAO {
    private final String JDBC_URL =
            "jdbc:h2:tcp://localhost/~/Hiroba";
    private final String DB_USER = "sa";
    private final String DB_PASS = "";

    public List<ShortMessage> findAll() {
        List<ShortMessage> messageList = new ArrayList<>();

        try {
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "JDBCドライバを読み込めませんでした");
        }

        try (Connection conn = DriverManager.getConnection(
                JDBC_URL, DB_USER, DB_PASS)) {

            String sql =
                    "SELECT ID, USER_NAME, TEXT FROM MESSAGES "
                    + "ORDER BY ID DESC";
            PreparedStatement pStmt = conn.prepareStatement(sql);

            ResultSet rs = pStmt.executeQuery();

            while (rs.next()) {
                int id = rs.getInt("ID");
                String userName = rs.getString("USER_NAME");
                String text = rs.getString("TEXT");

                ShortMessage message =
                        new ShortMessage(id, userName, text);
                messageList.add(message);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }

        return messageList;
    }

    public boolean create(ShortMessage message) {
        try {
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "JDBCドライバを読み込めませんでした");
        }

        try (Connection conn = DriverManager.getConnection(
                JDBC_URL, DB_USER, DB_PASS)) {

            String sql =
                    "INSERT INTO MESSAGES (USER_NAME, TEXT) "
                    + "VALUES (?, ?)";
            PreparedStatement pStmt = conn.prepareStatement(sql);
            pStmt.setString(1, message.getUserName());
            pStmt.setString(2, message.getText());

            int result = pStmt.executeUpdate();

            if (result != 1) {
                return false;
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }
}

MessagesDAO.javaは、com.example.demo.daoパッケージに作成します。

findAllメソッドでは、MESSAGESテーブルから投稿一覧を取得します。

取得した1件分のデータはShortMessageインスタンスにまとめ、messageListに追加します。

ORDER BY ID DESCを指定しているため、新しい投稿ほど上に表示されます。

createメソッドでは、ShortMessageインスタンスから投稿者名と投稿本文を取り出し、MESSAGESテーブルに追加します。

PreparedStatementを使って値を設定することで、SQL文と入力値を分けて扱えます。

サービスクラスを作成する

次に、ひとこと広場の処理を担当するServiceを作成します。

これまでLogicクラスとして作っていた処理担当クラスを、SpringではServiceとして作ります。

Serviceには@Serviceアノテーションを付けます。

作成するService

サービスクラス内容
LoginServiceログイン処理を担当する
PostMessageService投稿処理を担当する
GetMessageListService投稿一覧の取得を担当する

クラス名の末尾はServiceにします。

Serviceは、Controllerから呼び出されます。

ログインに関する処理をするサービスの例

ファイル名: LoginService.java

package com.example.demo.model;

import org.springframework.stereotype.Service;

@Service
public class LoginService {
    public boolean execute(User user) {
        if (user.getPass().equals("1234")) {
            return true;
        }
        return false;
    }
}

LoginService.javaは、com.example.demo.modelパッケージに作成します。

executeメソッドは、Userインスタンスを受け取ります。

ここでは、入力されたパスワードが1234であればログイン成功とします。

ログイン成功ならtrueを返します。

ログイン失敗ならfalseを返します。

実務では、ユーザー名とパスワードをデータベースで照合しますが、ここではSpring MVCの流れを確認するため、パスワードだけで判定するシンプルな処理にしています。

投稿に関する処理をするサービスの例

ファイル名: PostMessageService.java

package com.example.demo.model;

import org.springframework.stereotype.Service;

import com.example.demo.dao.MessagesDAO;

@Service
public class PostMessageService {
    public void execute(ShortMessage message) {
        MessagesDAO dao = new MessagesDAO();
        dao.create(message);
    }
}

PostMessageService.javaは、com.example.demo.modelパッケージに作成します。

executeメソッドは、ShortMessageインスタンスを受け取ります。

その中でMessagesDAOを作成し、createメソッドを呼び出します。

これにより、投稿者名と投稿本文がMESSAGESテーブルに追加されます。

今回は、これまで作成したDAOをそのまま利用する構成にしています。

投稿一覧を取得するサービスの例

ファイル名: GetMessageListService.java

package com.example.demo.model;

import java.util.List;

import org.springframework.stereotype.Service;

import com.example.demo.dao.MessagesDAO;

@Service
public class GetMessageListService {
    public List<ShortMessage> execute() {
        MessagesDAO dao = new MessagesDAO();
        List<ShortMessage> messageList = dao.findAll();
        return messageList;
    }
}

GetMessageListService.javaは、com.example.demo.modelパッケージに作成します。

executeメソッドでは、MessagesDAOのfindAllメソッドを呼び出します。

findAllメソッドから返された投稿一覧を、そのままControllerへ返します。

MainControllerは、この投稿一覧をリクエストスコープに保存し、main.jspで表示します。

コントローラクラスを作成する

次に、これまでサーブレットとして作成していたリクエスト処理を、SpringのControllerとして作成します。

ひとこと広場では、ログイン、ログアウト、メイン画面表示、投稿処理を扱います。

作成するController

Controller内容
LoginControllerログイン処理を担当する
LogoutControllerログアウト処理を担当する
MainControllerメイン画面の表示と投稿処理を担当する

Springでは、Controllerに@Controllerを付けます。

ハンドラメソッドには@GetMappingや@PostMappingを付けます。

サービスはnewで作成せず、コンストラクタインジェクションで利用します。

ログインに関するリクエストを処理するコントローラの例

ファイル名: LoginController.java

package com.example.demo.controller;

import jakarta.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.demo.model.LoginService;
import com.example.demo.model.User;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class LoginController {
    private final LoginService loginService;

    @PostMapping("/Login")
    public String login(
            @RequestParam String name,
            @RequestParam String pass,
            HttpSession session) {

        User user = new User(name, pass);

        boolean isLogin = loginService.execute(user);

        if (isLogin) {
            session.setAttribute("loginUser", user);
        }

        return "loginResult";
    }
}

LoginController.javaは、com.example.demo.controllerパッケージに作成します。

@PostMapping("/Login")により、ログインフォームからPOSTリクエストが送信されたときにloginメソッドが実行されます。

@RequestParam String nameで、ログイン画面のname属性がnameの入力値を受け取ります。

@RequestParam String passで、ログイン画面のname属性がpassの入力値を受け取ります。

HttpSessionを引数に定義しているため、Springがセッションスコープを扱うためのHttpSessionインスタンスを渡してくれます。

ログインに成功した場合は、UserインスタンスをloginUserという属性名でセッションスコープに保存します。

最後にloginResultを返し、loginResult.jspへフォワードします。

ログアウトに関するリクエストを処理するコントローラの例

ファイル名: LogoutController.java

package com.example.demo.controller;

import jakarta.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LogoutController {

    @GetMapping("/Logout")
    public String logout(HttpSession session) {

        session.invalidate();

        return "logout";
    }
}

LogoutController.javaは、com.example.demo.controllerパッケージに作成します。

@GetMapping("/Logout")により、LogoutへGETリクエストが送られたときにlogoutメソッドが実行されます。

logoutメソッドでは、session.invalidateを実行してセッションスコープを破棄します。

これにより、ログイン中のユーザー情報も削除されます。

最後にlogoutを返し、logout.jspへフォワードします。

投稿に関するリクエストを処理するコントローラの例

ファイル名: MainController.java

package com.example.demo.controller;

import java.util.List;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

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 com.example.demo.model.GetMessageListService;
import com.example.demo.model.PostMessageService;
import com.example.demo.model.ShortMessage;
import com.example.demo.model.User;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class MainController {

    private final GetMessageListService getMessageListService;
    private final PostMessageService postMessageService;

    @GetMapping("/Main")
    public String showMain(
            HttpServletRequest request,
            HttpSession session) {

        List<ShortMessage> messageList =
                getMessageListService.execute();
        request.setAttribute("messageList", messageList);

        User loginUser =
                (User) session.getAttribute("loginUser");

        if (loginUser == null) {
            return "redirect:index.jsp";
        } else {
            return "main";
        }
    }

    @PostMapping("/Main")
    public String postMessage(
            @RequestParam String text,
            HttpServletRequest request,
            HttpSession session) {

        User loginUser =
                (User) session.getAttribute("loginUser");

        if (loginUser == null) {
            return "redirect:index.jsp";
        }

        if (text != null && text.length() != 0) {
            ShortMessage message =
                    new ShortMessage(loginUser.getName(), text);
            postMessageService.execute(message);
        } else {
            request.setAttribute(
                    "errorMsg", "ひとことが入力されていません");
        }

        List<ShortMessage> messageList =
                getMessageListService.execute();
        request.setAttribute("messageList", messageList);

        return "main";
    }
}

MainController.javaは、com.example.demo.controllerパッケージに作成します。

@GetMapping("/Main")が付いたshowMainメソッドは、メイン画面を表示するときに実行されます。

まず、GetMessageListServiceを使って投稿一覧を取得します。

取得した投稿一覧は、messageListという属性名でリクエストスコープに保存します。

次に、セッションスコープからloginUserを取得します。

loginUserがnullであればログインしていない状態なので、index.jspへリダイレクトします。

loginUserが存在する場合は、main.jspへフォワードします。

@PostMapping("/Main")が付いたpostMessageメソッドは、投稿フォームからPOSTリクエストが送信されたときに実行されます。

まず、セッションスコープからloginUserを取得します。

ログインしていない場合は、index.jspへリダイレクトします。

投稿本文が入力されている場合は、ShortMessageインスタンスを作成し、PostMessageServiceに投稿処理を依頼します。

投稿本文が空の場合は、errorMsgという属性名でエラーメッセージをリクエストスコープに保存します。

最後に投稿一覧を再取得し、messageListとしてリクエストスコープに保存してからmain.jspへフォワードします。

図3:ログイン・投稿・ログアウトのリクエスト処理

この図から分かること

ログインフォームからPOST /Loginが送られると、LoginControllerが実行されます。

ログインに成功すると、UserインスタンスがloginUserという属性名でセッションスコープに保存されます。

メイン画面では、MainControllerが投稿一覧を取得し、messageListとしてリクエストスコープに保存します。

投稿フォームからPOST /Mainが送られると、MainControllerが投稿本文を受け取り、PostMessageServiceを通してMESSAGESテーブルに追加します。

ログアウトでは、LogoutControllerがセッションスコープを破棄し、logout.jspを表示します。

ビューを作成する

次に、JSPを作成します。

JSPでは、EL式とJSTLを使って、スコープに保存された値を表示します。

作成するJSPは、index.jsp、loginResult.jsp、main.jsp、logout.jspの4つです。

ログイン画面を表示するJSPの例

ファイル名: index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ひとこと広場</title>
</head>
<body>
<h1>ひとこと広場へようこそ</h1>
<form action="Login" method="post">
<p>
ユーザー名:
<input type="text" name="name">
</p>
<p>
パスワード:
<input type="password" name="pass">
</p>
<input type="submit" value="ログイン">
</form>
</body>
</html>

index.jspは、src/main/webappに配置します。

この画面では、ユーザー名とパスワードを入力します。

formタグのactionにはLoginを指定します。

methodにはpostを指定します。

ログインボタンをクリックすると、POST /Loginのリクエストが送信され、LoginControllerのloginメソッドが実行されます。

inputタグのname属性は、nameとpassにしています。

この名前は、LoginControllerの@RequestParam String name、@RequestParam String passと対応しています。

ログイン結果画面を表示するJSPの例

ファイル名: loginResult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ひとこと広場</title>
</head>
<body>
<h1>ログイン結果</h1>

<c:choose>
<c:when test="${not empty loginUser}">
<p>ログインに成功しました。</p>
<p>ようこそ<c:out value="${loginUser.name}" />さん</p>
<a href="Main">メイン画面へ</a>
</c:when>
<c:otherwise>
<p>ログインに失敗しました。</p>
<a href="index.jsp">戻る</a>
</c:otherwise>
</c:choose>

</body>
</html>

loginResult.jspは、src/main/webapp/WEB-INF/jspに配置します。

LoginControllerでは、ログイン成功時にUserインスタンスをloginUserという属性名でセッションスコープに保存します。

loginResult.jspでは、c:chooseを使ってログイン成功時と失敗時の表示を切り替えています。

loginUserが存在する場合は、ログイン成功として、ユーザー名をc:outで表示します。

loginUserが存在しない場合は、ログイン失敗として、index.jspへ戻るリンクを表示します。

メイン画面を表示するJSPの例

ファイル名: main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ひとこと広場</title>
</head>
<body>
<h1>ひとこと広場</h1>

<p>
<c:out value="${loginUser.name}" />さんログイン中
</p>

<p>
<a href="Main">更新</a>
<a href="Logout">ログアウト</a>
</p>

<form action="Main" method="post">
<p>
ひとこと:
<input type="text" name="text">
<input type="submit" value="投稿">
</p>
</form>

<c:if test="${not empty errorMsg}">
<p><c:out value="${errorMsg}" /></p>
</c:if>

<h2>投稿一覧</h2>

<c:forEach var="message" items="${messageList}">
<p>
<c:out value="${message.userName}" />:
<c:out value="${message.text}" />
</p>
</c:forEach>

</body>
</html>

main.jspは、src/main/webapp/WEB-INF/jspに配置します。

画面上部には、ログイン中のユーザー名を表示します。

loginUserはセッションスコープに保存されているため、${loginUser.name}で取得できます。

投稿フォームでは、inputタグのname属性をtextにしています。

この名前は、MainControllerの@RequestParam String textと対応しています。

投稿本文が空だった場合は、MainControllerがerrorMsgをリクエストスコープに保存します。

JSPでは、c:ifを使ってerrorMsgがある場合だけ表示します。

投稿一覧は、messageListをc:forEachで繰り返し表示します。

各投稿の投稿者名と本文は、c:outで安全に表示します。

ログアウト画面を表示するJSPの例

ファイル名: logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ひとこと広場</title>
</head>
<body>
<h1>ログアウト</h1>
<p>ログアウトしました。</p>
<a href="index.jsp">トップへ</a>
</body>
</html>

logout.jspは、src/main/webapp/WEB-INF/jspに配置します。

LogoutControllerでは、session.invalidateによってセッションスコープを破棄します。

その後、logout.jspへフォワードされます。

トップへリンクをクリックすると、index.jspへ戻ります。

Springアプリケーションを起動する

すべてのクラスとJSPを作成したら、Springアプリケーションを起動します。

Eclipseでは、Bootダッシュボードから起動します。

Springプロジェクトの起動手順

手順操作
1ウィンドウメニューを開く
2ビューの表示を選択する
3Boot ダッシュボードを選択する
4Boot ダッシュボードのlocalを展開する
5spring_Hirobaを右クリックする
6再起動または起動を選択する
7コンソールビューにSpringの起動メッセージが表示されることを確認する

Springプロジェクトを起動すると、Springに組み込まれたApache Tomcatが起動します。

これにより、ControllerやJSPに対してブラウザからリクエストを送信できるようになります。

すでに別のTomcatが起動している場合、ポート番号の競合でエラーになることがあります。

その場合は、Springダッシュボードやサーバービューで起動中のTomcatを停止してから、spring_Hirobaを起動し直します。

また、投稿一覧の取得や投稿登録ではH2 Databaseを使うため、H2 Databaseも起動しておきます。

ブラウザで動作確認する

Springアプリケーションを起動したら、ブラウザのアドレス欄に次のURLを入力します。

http://localhost:8080/index.jsp

index.jspが表示されたら、ユーザー名とパスワードを入力します。

ログイン確認

入力項目入力値の例
ユーザー名yuko
パスワード1234

パスワードが1234であればログイン成功です。

ログイン結果画面でメイン画面へのリンクをクリックすると、main.jspが表示されます。

メイン画面では、ひとことを入力して投稿できます。

投稿した内容は、MESSAGESテーブルに追加され、投稿一覧に表示されます。

動作確認で見るポイント

確認項目確認する内容
ログイン画面index.jspが表示されるか
ログイン成功パスワード1234でログイン成功になるか
ログイン失敗誤ったパスワードでログイン失敗になるか
メイン画面loginUserの名前が表示されるか
投稿処理入力したひとことがMESSAGESテーブルに追加されるか
投稿一覧messageListがc:forEachで表示されるか
入力エラー空の投稿でerrorMsgが表示されるか
ログアウトセッションスコープが破棄され、logout.jspが表示されるか

エラーが出た場合は、URL、Controllerの@GetMappingや@PostMapping、JSPの配置場所、application.propertiesの設定、H2 Databaseの起動状態、MESSAGESテーブルの有無を順番に確認します。

Servlet版からSpring版に変わったところ

今回作成したSpring版ひとこと広場は、これまでのServlet版と同じ機能を持っています。

ログインする。

ログイン結果を表示する。

メイン画面を表示する。

投稿する。

投稿一覧を表示する。

ログアウトする。

この流れは変わりません。

変わったのは、リクエスト処理の書き方です。

Servlet版とSpring版の違い

内容Servlet版Spring版
リクエスト処理Login、Logout、MainサーブレットLoginController、LogoutController、MainController
GET処理doGet@GetMapping
POST処理doPost@PostMapping
パラメータ取得request.getParameter@RequestParam
セッション取得request.getSessionHttpSessionを引数で受け取る
Serviceの利用newでLogicクラスを作るDIでServiceを注入する
フォワードRequestDispatcherreturnでビュー名を返す
リダイレクトresponse.sendRedirectreturn redirect:index.jsp
画面表示JSP、EL式、JSTLJSP、EL式、JSTL

Spring版では、Controllerの戻り値でフォワード先やリダイレクト先を指定します。

return "main"; と書けば、main.jspへフォワードされます。

return "redirect:index.jsp"; と書けば、index.jspへリダイレクトされます。

また、ServiceはController内でnewしません。

@RequiredArgsConstructorとfinalフィールドを使うことで、SpringがServiceインスタンスを自動的に注入してくれます。

Spring版ひとこと広場で押さえたいこと

Spring Frameworkは、JavaでWebアプリケーションを開発するときによく使われるフレームワークです。

Spring Bootを使うことで、Springを使った開発を始めやすくできます。

Webアプリケーションは、Controller、Service、Viewに分けて開発します。

Controllerは、@Controllerを付けた通常のJavaクラスとして作成します。

リクエストによって実行されるメソッドは、ハンドラメソッドと呼ばれます。

GETリクエストには@GetMapping、POSTリクエストには@PostMappingを使います。

リクエストパラメータは、@RequestParamを使ってハンドラメソッドの引数で受け取れます。

ハンドラメソッドの戻り値には、表示するビュー名やリダイレクト先を指定します。

Serviceは、@Serviceを付けた通常のJavaクラスとして作成します。

ControllerからServiceを利用するときは、依存性の注入を使います。

Viewは、JSPやThymeleafを使って作成できます。

今回は、これまで学んだJSP、EL式、JSTLを使って画面を作成しました。

ここから学びを広げるために

Spring版のひとこと広場を作成すると、ServletとJSPで学んだ知識がSpringでも活かせることが分かります。

リクエスト、レスポンス、セッションスコープ、リクエストスコープ、JSP、EL式、JSTL、DAO、MVCモデルの考え方は、Springでも土台になります。

Springを学ぶと、最初はアノテーションやDIなど新しい言葉が多く感じられるかもしれません。

しかし、裏側で行われていることをServletとJSPの流れに置き換えて考えると、少しずつ理解しやすくなります。

エラーが出たときも、どこでリクエストを受け取り、どこで処理を行い、どのスコープに保存し、どのJSPで表示しているのかを順番に確認すれば、原因を見つけやすくなります。

大切なのは、うまく動かない場面を避けることではありません。

エラーを1つずつ確認し、修正しながら、仕組みを自分の中に定着させていくことです。

ServletとJSPの基本を学んだ経験は、Springを理解するための大きな力になります。

ここまで学んできた知識を土台にして、Spring FrameworkによるWebアプリケーション開発へ、少しずつ進んでいきましょう。