サーブレット&JSPの基本|ひとこと広場の投稿と閲覧機能を作成する

投稿したひとことを全ユーザーで共有する。GETとPOSTを使い分けて、投稿と閲覧の機能を完成させよう

前回の記事では、ひとこと広場のメイン画面表示とログアウト機能を作成しました。

Main.javaでは、ログイン済みかどうかをセッションスコープのloginUserで確認し、ログインしていればmain.jspへフォワードしました。未ログインの場合は、トップ画面index.jspへリダイレクトしました。

また、メイン画面を表示する前に、アプリケーションスコープへmessageListを用意しました。messageListは、ひとこと投稿を保存するためのリストです。

今回の記事では、このmessageListを使って、ひとことの投稿と閲覧の機能を作成します。

利用者がメイン画面の入力欄に短い文章を入力し、投稿ボタンをクリックすると、その内容がMain.javaへPOSTリクエストとして送信されます。Main.javaでは、ログイン中の利用者名と入力された文章をもとにShortMessageインスタンスを作成します。

作成したShortMessageインスタンスは、アプリケーションスコープに保存されているmessageListの先頭に追加します。

そしてmain.jspでは、アプリケーションスコープからmessageListを取得し、リストの中に入っているひとことを順番に表示します。

これにより、ログインした利用者がひとことを投稿し、ほかの利用者の投稿も閲覧できるようになります。

今回作成する機能

今回作成するのは、ひとこと広場の中心となる投稿と閲覧の機能です。

作成する機能

機能内容
ひとこと投稿機能入力欄に書いた短い文章を投稿する
ひとこと閲覧機能投稿されたひとことを一覧で表示する
新しい順の表示新しく投稿されたひとことを上に表示する
全ユーザー共有投稿リストをアプリケーションスコープで共有する

今回の投稿機能では、ログイン中の利用者名と投稿内容を1つのShortMessageインスタンスにまとめます。

投稿されたShortMessageインスタンスは、messageListの先頭に追加します。

そのため、あとから投稿されたひとことほど、画面の上に表示されます。

今回作成・修正するファイル

今回の記事では、次の3つのファイルを作成または修正します。

ファイル一覧

ファイル種類役割
PostMessageLogic.javaモデルひとことをリストの先頭に追加する
Main.javaコントローラ 修正POSTで送信されたひとことを受け取り、リストへ追加する
main.jspビュー 修正投稿フォームとひとこと一覧を表示する

ShortMessage.javaとUser.javaは、1本目の記事で作成済みです。

Main.javaとmain.jspは、前回の記事で作成済みです。今回は、投稿と閲覧の機能を追加する形で修正します。

投稿と閲覧の全体像

ひとことの投稿は、メイン画面から始まります。

利用者が入力欄に文章を入力し、投稿ボタンをクリックすると、Main.javaへPOSTリクエストが送られます。

Main.javaは、入力された文章を取得し、セッションスコープからログイン中の利用者情報を取得します。

その後、利用者名と投稿内容を使ってShortMessageインスタンスを作成し、PostMessageLogic.javaに処理を依頼してmessageListの先頭へ追加します。

投稿処理の流れ

順番内容
1main.jspでひとことを入力する
2投稿ボタンをクリックする
3Main.javaのdoPostメソッドが実行される
4リクエストパラメータtextを取得する
5セッションスコープからloginUserを取得する
6アプリケーションスコープからmessageListを取得する
7ShortMessageインスタンスを作成する
8PostMessageLogicでmessageListの先頭に追加する
9main.jspへフォワードする
10投稿一覧が表示される

この流れにより、投稿したひとことがメイン画面に表示されます。

図1:ひとこと投稿の流れ

この図から分かること

ひとこと投稿では、main.jspのフォームからMain.javaへPOSTリクエストが送られます。

Main.javaでは、入力された投稿内容をrequest.getParameter("text")で取得します。また、セッションスコープからloginUserを取得し、誰が投稿したのかを確認します。

投稿内容と利用者名をもとにShortMessageインスタンスを作成し、PostMessageLogic.javaでmessageListの先頭へ追加します。

messageListはアプリケーションスコープに保存されているため、すべての利用者で共有できます。

Main.javaはGETとPOSTで役割を分ける

前回作成したMain.javaは、GETリクエストを受け取ってメイン画面を表示するサーブレットでした。

今回の記事では、同じMain.javaにPOSTリクエストを処理するdoPostメソッドを追加します。

つまり、Main.javaはGETとPOSTで異なる役割を持つことになります。

Main.javaの役割

リクエスト方法実行されるメソッド役割
GETdoGetメイン画面を表示する
POSTdoPost投稿されたひとことを登録する

GETは、メイン画面を表示するときに使います。

POSTは、入力されたひとことを送信して登録するときに使います。

このように、同じサーブレットでも、GETで呼ばれたときとPOSTで呼ばれたときで別の処理を行わせることができます。

投稿内容はリクエストパラメータで受け取る

main.jspの投稿フォームでは、inputタグのname属性にtextを指定します。

そのため、Main.javaでは次のように投稿内容を取得します。

request.getParameter("text")

フォームとサーブレットの対応

main.jsp側Main.java側
inputタグのname属性 textrequest.getParameter("text")
formタグのaction属性 MainURLパターン/Mainのサーブレットが実行される
formタグのmethod属性 postdoPostメソッドが実行される

name属性とgetParameterの引数は一致させる必要があります。

ここがずれていると、入力欄に文字を入力しても、サーブレット側では値を取得できません。

空の投稿は追加しない

今回の投稿処理では、入力されたtextが空でない場合だけ、投稿を追加します。

Main.javaでは、次のように確認します。

text != null && text.length() != 0

入力値チェックの考え方

状態処理
textに文字が入っているShortMessageを作成してmessageListへ追加する
textがnull投稿を追加しない
textの文字数が0投稿を追加しない

今回の記事では、空の投稿を送信しても、ひとこと一覧には追加しないだけにします。

空投稿時にエラーメッセージを表示する機能は、次の記事で追加します。

ひとことを追加するモデルを作成する

投稿されたひとことをmessageListへ追加する処理は、PostMessageLogic.javaに分けます。

PostMessageLogic.javaは、ShortMessageインスタンスとmessageListを受け取り、messageListの先頭にShortMessageインスタンスを追加します。

ひとことの投稿に関する処理を行うモデルの例

ファイル名: PostMessageLogic.java

package model;

import java.util.List;

public class PostMessageLogic {

    public void execute(ShortMessage message,
            List<ShortMessage> messageList) {
        messageList.add(0, message);
    }
}

PostMessageLogic.javaは、modelパッケージに作成します。

executeメソッドでは、messageList.add(0, message)を使って、リストの先頭に投稿を追加しています。

PostMessageLogic.javaのポイント

PostMessageLogic.javaは、ひとことをリストへ追加する処理だけを担当します。

画面表示は行いません。

リクエストパラメータの取得も行いません。

PostMessageLogic.javaの役割

項目内容
受け取るものShortMessageインスタンスとList
行う処理messageListの先頭にShortMessageを追加する
使うメソッドmessageList.add(0, message)
戻り値なし

messageList.add(0, message)では、第1引数に追加位置、第2引数に追加するインスタンスを指定しています。

0はリストの先頭を表します。

そのため、新しい投稿がリストの先頭に入り、画面では新しい投稿が上に表示されます。

ArrayListのaddで先頭に追加する

ArrayListのaddには、要素を末尾に追加する使い方と、指定した位置に挿入する使い方があります。

今回使っているのは、指定した位置に挿入する使い方です。

messageList.add(0, message)

addの指定

指定内容
第1引数 0追加する位置。0は先頭を表す
第2引数 message追加するShortMessageインスタンス

すでにリストに投稿が入っている場合、先頭に新しい投稿を追加すると、既存の投稿は1つずつ後ろにずれます。

上書きされるわけではありません。

先頭に追加されるイメージ

操作前の順番投稿
0番目山田:今日もServletの復習をしました
1番目青山:JSPの画面表示が分かってきました

新しい投稿として、佐藤:エラーが解決しましたをmessageList.add(0, message)で追加すると、次のようになります。

操作後の順番投稿
0番目佐藤:エラーが解決しました
1番目山田:今日もServletの復習をしました
2番目青山:JSPの画面表示が分かってきました

この仕組みにより、新しいひとことを上に表示できます。

図2:messageListの先頭に新しい投稿を追加する

この図から分かること

messageList.add(0, message)を使うと、新しいShortMessageインスタンスをリストの先頭に追加できます。

すでにリストに入っている投稿は上書きされず、1つずつ後ろにずれます。

そのため、投稿一覧を先頭から順に表示すれば、新しい投稿ほど上に表示されます。

ひとこと広場では、この仕組みを使って、新着順の投稿一覧を作ります。

Main.javaにdoPostを追加する

次に、Main.javaへ投稿処理を追加します。

前回作成したdoGetメソッドはそのまま使います。

今回追加するのは、POSTリクエストを処理するdoPostメソッドです。

doPostで行う処理

順番処理内容
1文字コードを指定するrequest.setCharacterEncoding("UTF-8")を実行する
2textを取得する投稿内容をリクエストパラメータから取得する
3空でないか確認する入力されている場合だけ投稿処理を行う
4messageListを取得するアプリケーションスコープから投稿リストを取得する
5loginUserを取得するセッションスコープからログイン中の利用者を取得する
6ShortMessageを作成する利用者名と投稿内容をまとめる
7PostMessageLogicを実行するmessageListの先頭へ投稿を追加する
8messageListを保存するアプリケーションスコープへ保存する
9main.jspへフォワードする更新後のメイン画面を表示する

doPostでは、投稿内容が空でない場合だけmessageListへ追加します。

空の場合は、今回の記事では何も追加せず、そのままmain.jspへフォワードします。

ひとことに関するリクエストを処理するコントローラを修正する

Main.javaにdoPostメソッドを追加します。

前回作成したdoGetメソッドは変更しないため、ここでは省略して、追加するdoPostメソッドを中心に示します。

ひとことに関するリクエストを処理するコントローラを修正した例

ファイル名: Main.java

package servlet;

import java.io.IOException;
import java.util.List;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import model.PostMessageLogic;
import model.ShortMessage;
import model.User;

@WebServlet("/Main")
public class Main extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        // ひとことリストをアプリケーションスコープから取得
        ServletContext application = this.getServletContext();

        List<ShortMessage> messageList =
                (List<ShortMessage>) application.getAttribute("messageList");

        // 取得できなかった場合は、ひとことリストを新規作成して
        // アプリケーションスコープに保存
        if (messageList == null) {
            messageList = new ArrayList<>();
            application.setAttribute("messageList", messageList);
        }

        // ログインしているか確認するため、
        // セッションスコープからユーザー情報を取得
        HttpSession session = request.getSession();

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

        if (loginUser == null) {
            // ログインしていない場合はトップ画面へリダイレクト
            response.sendRedirect("index.jsp");
            return;

        } else {
            // ログイン済みの場合はメイン画面へフォワード
            RequestDispatcher dispatcher =
                    request.getRequestDispatcher(
                            "WEB-INF/jsp/main.jsp");
            dispatcher.forward(request, response);
        }
    }

    
    
    @SuppressWarnings("unchecked")
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        // リクエストパラメータの取得
        request.setCharacterEncoding("UTF-8");
        String text = request.getParameter("text");

        // 入力値チェック
        if (text != null && text.length() != 0) {

            // アプリケーションスコープに保存されたひとことリストを取得
            ServletContext application = this.getServletContext();

            List<ShortMessage> messageList =
                    (List<ShortMessage>)
                            application.getAttribute("messageList");

            // セッションスコープに保存されたユーザー情報を取得
            HttpSession session = request.getSession();

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

            // ひとことを作成してひとことリストに追加
            ShortMessage message =
                    new ShortMessage(loginUser.getName(), text);

            PostMessageLogic postMessageLogic =
                    new PostMessageLogic();
            postMessageLogic.execute(message, messageList);

            // アプリケーションスコープにひとことリストを保存
            application.setAttribute("messageList", messageList);
        }

        // メイン画面にフォワード
        RequestDispatcher dispatcher =
                request.getRequestDispatcher("WEB-INF/jsp/main.jsp");
        dispatcher.forward(request, response);
    }
}

Main.javaは、servletパッケージに作成済みのファイルを修正します。

この例では、前回作成したdoGetメソッドは省略しています。実際のファイルでは、前回のdoGetメソッドを残したまま、doPostメソッドを追加します。

Main.javaのdoPostのポイント

doPostでは、投稿処理に必要な情報を3つ扱っています。

doPostで扱う情報

情報取得元用途
textリクエストパラメータ投稿内容として使う
loginUserセッションスコープ誰が投稿したかを判断する
messageListアプリケーションスコープ投稿を追加するリストとして使う

textは、main.jspの入力欄から送信されます。

loginUserは、ログイン成功時にセッションスコープへ保存されたUserインスタンスです。

messageListは、Main.javaのdoGetで用意したアプリケーションスコープ内の投稿リストです。

この3つを組み合わせることで、誰が何を投稿したのかをShortMessageインスタンスとして作成できます。

アプリケーションスコープのmessageListを更新する

投稿されたひとことは、アプリケーションスコープのmessageListに追加します。

messageListは、全ユーザーで共有する投稿一覧です。

そのため、山田さんが投稿したあとに青山さんがログインしても、山田さんの投稿を閲覧できます。

messageListをアプリケーションスコープに保存する理由

理由内容
全ユーザーで共有するためほかの利用者の投稿も表示できる
リクエストをまたいで保存するため投稿後に再表示しても一覧に残る
メイン画面で共通利用するためmain.jspで一覧表示に使う

今回のMain.javaでは、投稿を追加したあとにapplication.setAttribute("messageList", messageList)を実行しています。

getAttributeで取得したリストに追加した時点で、アプリケーションスコープ内のリストにも変更が反映されます。ただし、学習段階では、更新後にsetAttributeで保存し直す流れとして覚えると、処理の流れが分かりやすくなります。

メイン画面に投稿フォームと一覧を追加する

次に、main.jspを修正します。

前回のmain.jspでは、ログイン中の利用者名とログアウトリンクだけを表示していました。

今回は、次の内容を追加します。

main.jspに追加する内容

追加する内容役割
更新リンクMainへGETリクエストを送り、画面を再表示する
投稿フォームひとことを入力してMainへPOST送信する
投稿一覧messageListに入っているひとことを表示する

投稿フォームでは、formタグのaction属性にMain、method属性にpostを指定します。

これにより、投稿ボタンをクリックしたときに、Main.javaのdoPostメソッドが実行されます。

メイン画面を出力するビューを修正する

main.jspを次のように修正します。

メイン画面を出力するビューを修正した例

ファイル名: main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<%@ page import="model.User, model.ShortMessage, java.util.List" %>
<%
    // セッションスコープに保存されたユーザー情報を取得
    User loginUser =
            (User) session.getAttribute("loginUser");

    // アプリケーションスコープに保存されたひとことリストを取得
    List<ShortMessage> messageList =
            (List<ShortMessage>)
                    application.getAttribute("messageList");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ひとこと広場</title>
</head>
<body>
<h1>ひとこと広場メイン</h1>

<p>
<%= loginUser.getName() %>さん、ログイン中
<a href="Logout">ログアウト</a>
</p>

<p><a href="Main">更新</a></p>

<form action="Main" method="post">
<input type="text" name="text">
<input type="submit" value="投稿">
</form>

<% for (ShortMessage message : messageList) { %>
<p><%= message.getUserName() %>:<%= message.getText() %></p>
<% } %>

</body>
</html>

main.jspは、src/main/webapp/WEB-INF/jspディレクトリにあるファイルを修正します。

このJSPでは、セッションスコープからloginUserを取得し、アプリケーションスコープからmessageListを取得しています。

その後、投稿フォームと投稿一覧を表示しています。

main.jspのポイント

main.jspでは、User、ShortMessage、Listを使うため、pageディレクティブでインポートしています。

インポートするクラス

クラス使う理由
model.Userログイン中の利用者情報を扱うため
model.ShortMessageひとこと投稿を扱うため
java.util.List投稿リストを扱うため

messageListは、アプリケーションスコープから取得します。

JSPでは、アプリケーションスコープを操作するために、暗黙オブジェクトapplicationを使えます。

application.getAttribute("messageList")で取得した戻り値はObject型なので、Listへキャストしています。

投稿フォームのしくみ

main.jspの投稿フォームは、次のようになっています。

フォームの指定

指定内容
action属性 MainMain.javaへリクエストを送る
method属性 postPOSTで送信する
inputタグのname属性 text投稿内容をtextという名前で送る
submitボタンクリックするとフォームを送信する

投稿ボタンをクリックすると、Main.javaのdoPostメソッドが実行されます。

doPostでは、request.getParameter("text")で入力されたひとことを取得します。

拡張for文で投稿一覧を表示する

main.jspでは、messageListに入っているShortMessageインスタンスを、拡張for文で順番に取り出しています。

<% for (ShortMessage message : messageList) { %>

この文では、messageListに入っているShortMessageインスタンスが、1つずつmessage変数に代入されます。

拡張for文の考え方

部分内容
ShortMessageリストから取り出すインスタンスの型
message取り出したインスタンスを入れる変数
messageList繰り返しの対象になるリスト

ループの中では、message.getUserNameで投稿者名を取得し、message.getTextで投稿内容を取得しています。

これにより、投稿者名:投稿内容という形で画面に表示できます。

図3:main.jspでひとこと一覧を表示する流れ

この図から分かること

main.jspでは、アプリケーションスコープからmessageListを取得します。

messageListには、投稿されたShortMessageインスタンスが保存されています。

拡張for文を使ってmessageListの先頭から順に取り出すことで、ひとこと一覧を画面に表示できます。

PostMessageLogic.javaで新しい投稿を0番目に追加しているため、画面にも新しい投稿から順に表示されます。

更新リンクの役割

main.jspには、次の更新リンクを追加しています。

更新

このリンクをクリックすると、Main.javaへGETリクエストが送られます。

GETリクエストなので、Main.javaのdoGetメソッドが実行され、現在のmessageListを使ってメイン画面が再表示されます。

更新リンクの動き

操作実行される処理
更新リンクをクリックするMainへGETリクエストを送る
Main.javadoGetメソッドを実行する
main.jsp現在の投稿一覧を表示する

この更新リンクは、投稿一覧を再表示したいときに使います。

別の利用者が投稿した内容を確認したい場合にも、更新リンクをクリックすることで最新のmessageListを表示できます。

投稿と閲覧機能の動作確認

ここまで作成できたら、投稿と閲覧の機能を確認します。

まずは、1人の利用者でログインし、複数のひとことを投稿してみましょう。

1人の利用者で確認する手順

手順内容
1ブラウザでhttp://localhost:8080/Hiroba/へアクセスする
2任意の利用者名とパスワード1234でログインする
3ログイン結果画面からひとこと投稿・閲覧へをクリックする
4メイン画面の入力欄に1つ目のひとことを入力する
5投稿ボタンをクリックする
6投稿したひとことが表示されることを確認する
72つ目のひとことを投稿する
82つ目のひとことが1つ目の上に表示されることを確認する
9空のまま投稿する
10空の投稿が一覧に追加されないことを確認する

新しい投稿が上に表示されれば、messageList.add(0, message)が正しく動作しています。

空の投稿が追加されなければ、text.length() != 0のチェックが働いています。

ブラウザの表示例

複数ユーザーで共有されることを確認する

次に、アプリケーションスコープに保存したmessageListが、複数の利用者で共有されることを確認します。

複数ユーザーで確認する手順

手順内容
1ユーザーAでログインする
2ユーザーAとしてひとことを投稿する
3ユーザーAをログアウトする
4ブラウザを閉じる
5ユーザーBでログインする
6ユーザーAの投稿が表示されることを確認する
7ユーザーBとしてひとことを投稿する
8ユーザーBの投稿がユーザーAの投稿より上に表示されることを確認する
9ユーザーBをログアウトする
10再度ユーザーAでログインする
11ユーザーAとユーザーBの投稿が表示されることを確認する

messageListはアプリケーションスコープに保存されているため、ユーザーごとのセッションスコープとは違い、全ユーザーで共有されます。

そのため、ユーザーAが投稿した内容を、ユーザーBも閲覧できます。

ブラウザの表示例:山田の投稿画面

ブラウザの表示例:青山の当行画面

アプリケーションスコープに保存した投稿の有効期間

messageListはアプリケーションスコープに保存しています。

そのため、Webアプリケーションが動いている間は、投稿されたひとことが残ります。

ただし、サーバを停止したり再起動したりすると、アプリケーションスコープに保存されていたmessageListは消滅します。

投稿が残る場合と消える場合

操作投稿一覧
同じブラウザで更新する残る
ブラウザを閉じて再度ログインするWebアプリケーションが動いていれば残る
別の利用者でログインするWebアプリケーションが動いていれば表示される
サーバを停止する消える
サーバを再起動する消える
Webアプリケーションが再読み込みされる消えることがある

今回は、データベースやファイル保存を使っていません。

そのため、投稿内容を長期的に保存することはできません。アプリケーションスコープは、Webアプリケーションが動いている間だけ利用できる共有領域です。

今回作成したファイル

今回の記事では、ひとこと広場の投稿と閲覧機能を作成しました。

作成・修正したファイル

ファイル保存場所内容
PostMessageLogic.javasrc/main/java/modelひとことをmessageListの先頭に追加する
Main.javasrc/main/java/servletdoPostを追加し、投稿処理を行う
main.jspsrc/main/webapp/WEB-INF/jsp投稿フォームと投稿一覧を表示する

今回のポイントは、Main.javaがGETとPOSTで異なる動きをすることです。

GETで呼ばれたときはメイン画面を表示し、POSTで呼ばれたときは投稿処理を行います。

また、投稿一覧はアプリケーションスコープのmessageListに保存します。これにより、ひとこと広場を利用するすべてのユーザーで投稿を共有できます。

次の記事では、空の投稿を送信したときに、エラーメッセージを表示する機能を追加します。エラーメッセージは、フォワード先のmain.jspで一度だけ使う情報なので、リクエストスコープに保存して表示します。