プロジェクト

全般

プロフィール

Jakarta EE Servlet and JSP

はじめに

Java読書会BOF主催の 「基礎からのサーブレット/JSP 第5版」 を読む会の勉強メモとして記載します。
Servlet 6.0, JSP 3.1仕様を、Tomcat 10で動かします。

開発環境

書籍のサンプルは、ユーザーディレクトリ下にソースコードとTomcatを展開し、ソースコードはTomcatのwebapps/book/WEB-INF/src下に作成し、コンパイル結果のクラスファイルを同 WEB-INF/classes 下に生成する構成となっています。書籍は初心者向けをターゲットとしているので、環境構築でつまずくことのないようにとの配慮で構成されています。

この記事では、一般的な開発・実行環境を把握しておきたいので、Webアプリケーションをwarファイルとして生成し、webappsの下に配備する一般的な構成とします。アプリケーションのビルトには、Gradleを使用します。IDEはIntelliJを使いますが、Gradleに対応できる他の開発環境でも可能です。

Windows 11

  • OpenJDK 21
  • Gradle 8.7
  • Tomcat 10.1.20
  • H2 database 2.2.224
  • IntelliJ IDEA 2023.3.6

macOS 13.4

  • OpenJDK 21
  • Gradle 8.7
  • Tomcat 10.1.20
  • H2 database 2.2.224
  • IntelliJ IDEA 2024.1

OpenJDK、Gradle、Tomcat、H2は、Homebrewでインストールしています。
IntelliJ IDEAは、アプリケーションのインストーラでインストール(と記憶)しています。

Homebrewでインストールしたtomcatは、webappsが /opt/homebrew/opt/tomcat/libexec/webapps に存在します。

知識編

サーブレット、JSPおよびWebアプリケーションに関する知識の記録です。

サーブレットとJSPの違い

 Java Servlet     Java Server Page
+------------+   +-------------+
| Java code  |   | HTML code   |
| +--------+ |   | +---------+ |
| | HTML   | |   | |Java code| |
| +--------+ |   | +---------+ |
| +--------+ |   | +---------+ |
| | HTML   | |   | |Java code| |
| +--------+ |   | +---------+ |
| +--------+ |   | +---------+ |
| | HTML   | |   | |Java code| |
| +--------+ |   | +---------+ |
+------------+   +-------------+

ServletはJavaのコードの中にHTMLのコードが埋め込まれている(print文の文字列がHTMLコード)のに対して、JSPはHTMLコードの中にJavaのコードが埋め込まれているという形態です。

サーブレット

サーブレットクラスの実装

  • java.servlet.http.HttpServletを継承する
  • doGetやdoPostメソッドを実装して、HTTPのリクエスト・レスポンス処理を記述する
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class Reserve extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        : HTTP GETメソッドによるリクエストを受けてレスポンスを返す
    }
}
  • 引数 HttpServletRequest には、リクエストパラメータ、リクエスト属性、セッション、クッキーなどが格納
  • レスポンスは、HTML文書(テキスト)を返すのが基本
  • レスポンスで返すテキストは、引数 HttpServletResponse オブジェクトからgetWriter()で取得したPrintWriterを使って出力
  • HttpServletResponseにクッキーの設定が可能
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8"); // (1)
        var out = resp.getWriter();
        :
    }
  • レスポンスで返すHTMLテキストのMIMEタイプ設定をする。一般的にはHTMLがUTF-8文字符号化されるので "text/html; charset=UTF-8"を設定
    この設定は、getWriterの呼び出し前に行う
protected修飾子

HttpServletのdoGet/doPostメソッドは、protected修飾子で定義されています。書籍ではサーブレットアプリケーションのdoGet/doPostメソッドはpublicとして記述されています。
doGet/doPostメソッドは、serviceメソッドから呼び出され、外部のクラスからの呼び出しはないので、本記事ではprotecedのままとしています。

リクエストパラメータ

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.setCharacterEncoding("UTF-8");                   // (1)
    String count = req.getParameter("count");            // (2)
    String[] values = req.getParameterValues("option");  // (3)

  • (1) リクエストパラメータのテキストのエンコーディングを指定
  • (2) リクエストパラメータの名前を指定して値を取得(同じ名前でリクエストが1つの場合)
  • (3) リクエストパラメータの名前を指定して複数の値を取得(同じ名前でリクエストが複数の場合、チェックボックス等)

HTMLファイル

HTMLの規格と書き方

HTMLの規格は現在 WHATWGが策定する HTML Living Standardとして標準化されています。

HTML記述例 説明
<!DOCTYPE html> DOCTYPE宣言で、htmlと指定します
<html> トップ要素は html です。lang属性でHTMLファイルを記述する言語(日本語、英語、など)を指定するとブラウザの自動判別に頼らず意図する表示になります。
<head> html要素の子要素で、HTML文書のメタデータを記述します。題名、スクリプト、スタイルシート、文字コードなど
<meta charset="UTF-8"> head要素の子要素で、HTMLファイルの文字コードを指定します。
<title> head要素の子要素でHTMLの題名を指定します。ブラウザで表示するときにはタブの名称、ブックマーク名などに使用されます。
<body> html要素の子要素で、HTMLコンテンツを記述する要素です。

HTMLのフォームとサーブレット呼び出し

サーブレットの典型的な使い方の一つに、HTMLのフォームで入力した情報をサーブレットが処理するというものがあります。

<form action="reserve" method="post">
    <p>レストランをご予約ください</p>
    <p>人数
        <select name="count">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
        </select>
    </p>

    <p>座席
        <input type="radio" name="seat" value="禁煙" checked>禁煙
        <input type="radio" name="seat" value="喫煙">喫煙
    </p>

    <p>オプション
        <input type="checkbox" name="option" value="ケーキ">ケーキ
        <input type="checkbox" name="option" value="花束">花束
    </p>

    <p><input type="submit" value="予約"></p>
</form>
  • form要素のaction属性で、フォームに入力した情報をリクエストするURL(相対URL)とHTTPメソッド(GETまたはPUT)を指定
  • input要素は、type属性で指定した形式のユーザー入力をHTML画面上に設定
    • radioはラジオボタンで、name属性の値が同じラジオボタンが一つのグループとなりグループで1つだけがチェック可能、またname属性の値がリクエストパラメータの名前となり、value属性の値がリクエストパラメータの値となる
    • checkboxはチェックボックスで、name属性の値がリクエストパラメータの名前、valueがリクエストパラメータの値となる。複数のチェックボックスが選択されたとき、それぞれチェックボックスが同じname属性の値を持つと、同じリクエストパラメータの名前で複数のリクエストが送信される
    • submitは、入力したデータをまとめてサーバーに送信するボタンを生成する。

コンテキストパス、URLパターン

WebアプリケーションへアクセスするURLは、
http://localhost:8080/book/hello
                     ^^^^^
  • /book がコンテキストパスでサーバー内でWebアプリケーションを識別します。
  • /hello はサーブレットの指定で、アノテーションまたはweb.xmlでURLパターンとして指定し特定のサーブレットと紐づけます。
http://localhost:8080 /book /hello
サーバーURL コンテキストパス リソースURL

アノテーションか設定ファイルか

楽なのはアノテーションですね。1行の記述で済みます。一方、web.xmlで記述する場合、FQCNクラスメイトの対応付け、URLパターンとの対応付けをそれぞれXMLのタグ付き記述しなくてはならないです。

一方、誰が作ったかソースコードをすぐに参照できないようなWebアプリケーションがあるとアノテーションで定義したURLパターンを確認するのが大変ということがあるかもしれません。

どちらが良いかは、トレードオフになります。

JSP

HTML文書の中にJavaコードを埋め込んだファイルです。実行時にJSPファイルからサーブレットのコードを生成しコンパイルされ、サーブレットとして実行されます。
JSPは、プログラマーではないユーザーインタフェースデザイナーがHTMLをベースに画面デザインを実施できることをねらいとしています。

シンプルなJSP

最低限のJSP
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>Title</title>
</head>
<body>

<%-- メッセージの出力 --%>
<p>Hello!</p>
<p>こんにちは</p>

</body>
</html>
  • <% ~ %> がJSPのディレクティブで、JSP特有の機能を記述
  • <%-- ~ --%> は、JSPのコメントでHTMLには出力されません。HTMLに出力したいコメントは、HTMLのコメント を使います
  • @pageディレクティブは、JSPページ全体の設定(MIMEタイプなど)
共通部分を別ファイルに記述

@includeディレクティブで共通のHTML記述を別ファイルから取り込みます。
Javaのコード(ステートメント)を、スクリプトレット <% ~ %> に記載しています。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="../header.html" %>

<p>Hello!</p>
<p>こんにちは!</p>

<p><% out.println(java.time.ZonedDateTime.now()); %></p>

<%@ include file="../footer.html" %>
  • header.html
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Servlet/JSP Samples</title>
    </head>
    <body>
    
  • footer.html
    </body>
    </html>
    

次のスクリプトレットにはJavaのコードを直接記述できます。

<% out.println(java.time.ZonedDateTime.now()); %>

outは、暗黙に定義されたオブジェクトで、レスポンスのテキスト出力に使います。

スクリプトレットとは別に、Javaの式を記述すると評価結果が展開される式も記述できます。

<%= java.time.ZonedDateTime.now() %>

上述のスクリプトレットおよび式では、ZonedDateTimeクラスを利用するためにFQCN(パッケージ名付きのクラス名)を記述しましたが、Javaプログラムと同様にimportで使用するクラスを宣言することもできます。importは、ページディレクティブ <%@ ~ %>で記述します。

<%@ page import="java.time.ZonedDateTime" %>
  :
<p><%= ZonedDateTime.now() %></p>

フィルタ

それぞれのサーブレットやJSPに同じコードを繰り返し記述する代わりに、フィルターを定義して複数のサーブレットやJSPに適用することができます。
例えば、サーブレットのリクエスト(doGetやdoPost)の際に決まった処理を実行するなどです。

jakarta.servlet.Filterクラスを継承してフィルタを定義します。フィルタの適用対象は、フィルタクラスのアノテーション @WebFilter で指定するか、web.xmlに指定します。複数のフィルタを指定する場合にフィルタの実行順序を指定したい場合は、web.xmlの記述が必要です。

データベース

Webアプリケーションからデータベースを利用します。
SQLでのアクセスは、JDBC APIを使用します。JDBCでは、データソースに定義されたデータベースへ接続し、SQLを介してデータベースとやり取りをします。

  • データソースの設定
  • JDBCドライバの配置
  • JDBC接続

データソースの設定

Tomcatの場合、アプリケーションのMETA-INF/context.xmlにデータソースの定義を記述します。

<?xml version="1.0" encoding="UTF-8" ?>
<Context>
    <Resource
        name="jdbc/book" 
        auth="Container" 
        type="javax.sql.DataSource" 
        driverClassName="org.h2.Driver" 
        url="jdbc:h2:tcp://localhost/~/work/h2/book" 
        username="sa" 
        password="password" 
    />
</Context>
  • nameはサーブレット/JSPからデータソースを取得する際に使用
  • authは認証を誰が行うかを指定、ContainerはWebコンテナ(Tomcat)が実施、Applicationはアプリケーションが実施
  • typeは、JDBCの場合、javax.sql.DataSourceを指定
  • driverClassNameは接続に使用するJDBCドライバのクラス名を指定
  • urlは接続文字列
  • usernameとpasswordは認証に使用するユーザー名とパスワード

JDBCドライバの配置

JDBCドライバは、アプリケーションサーバー(Webコンテナ)のライブラリ配置場所にあらかじめ配置しておくか、Webアプリケーションと一緒に配布するかの方法があります。

アプリケーションサーバーに配置する場合は、Tomcatの場合は、$CATALINA_HOME/libの下に置くことになります。

TomcatのJDBC Connection poolを使用する場合、connection poolのライブラリ(jar)と同じクラスローダーでJDBCドライバーをロードする必要があります。その場合、JDBCドライバは$CATALINA_HOME/libに配置します。

実践編

実際にサーブレット、JSPを動かしてみます。書籍の内容をもとに、HTMLファイルやサーブレットのJavaクラスをwarファイルに生成して tomcat に配備してブラウザからアクセスします。

tomcat の実行

Windows

パッケージ管理 scoop で、tomcatとoraclejdkをインストールします。scoopでインストールすると、コマンドへのPATHが設定されます。

コマンドプロンプトを開き、文字コードをUTF-8に設定します。

D:\work\book> chcp 65001

tomcatを実行します。

D:\work\book> catalina run
  :

macOS

パッケージ管理 Homebrew で、tomcatとopenjdkをインストールします。

tomcatのコマンドライン環境での起動は

% /opt/homebrew/opt/tomcat/bin/catalina run

HTMLとサーブレットのプロジェクト

Gradleプロジェクトの作成

D:\work> mkdir book
D:\work> cd book
D:\work\book>

D:\work\book\build.gradle.kts ファイルを作成します。
warプラグインを指定します。
サーブレットAPIのライブラリと、アノテーションAPIのライブラリを依存関係に定義し、リポジトリをmaven centralに指定します。

plugins {
    war
}

repositories {
    mavenCentral()
}

dependencies {
    providedCompile("jakarta.servlet:jakarta.servlet-api:6.0.0")
    providedCompile("jakarta.annotation:jakarta.annotation-api:2.1.1")
}

warプラグインが使用するデフォルトのソースファイル等を収容するディレクトリを作成します。

D:\work\book> mkdir src\main\java
D:\work\book> mkdir src\main\resources
D:\work\book> mkdir src\main\webapp
  • src\main\javaディレクトリ以下のJavaソースファイルはコンパイルされてclassファイルがwarファイルのWEB-INF/classes 下にパッケージ階層に応じて配置されます。
  • src\main\resourcesディレクトリ以下のファイルは、そのまま warファイルのWEB-INF/classes下にディレクトリ階層に応じて配置されます。
  • src\main\webappディレクトリに置いたファイルは、そのまま warファイルのルートディレクトリ以下に配置されます。HTMLファイルやJSPファイルはこの配下に作成します。
  • 注)WEB-INF/web.xml として配置したい場合は、src\main\webapp\WEB-INF\web.xml に作成する。src\maini\resources\web.xmlに作成すると、warファイルのWEB-INF/classes/web.xml に配置されてしまう。

HTMLファイルの生成

src\main\webapp\index.html を作成します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Servlet/JSP book</title>
</head>
<body>
Welcome!
</body>
</html>

warの生成と配備

HTMLファイルを1つ作成した段階でwarを生成し、tomcatに配備します。

  • Gradleでwarファイルを生成
    D:\work\book> gradlew war
      :
    D:\work\book> dir build\libs
    2024/04/04  20:35               492 book.war
    
  • warファイルをtomcatに配備
    D:\work\book> copy build\libs\book.war %USERPROFILE%\scoop\persist\tomcat\webapp
    

ブラウザでbookアプリケーションのHTMLを表示

http://localhost:8080/book/

にWebブラウザでアクセスすると、Welcome! と表示されます。

サーブレットプログラムの作成(annotation使用)

annotation で URLパターンを指定するサーブレット。
src\main\java\com\torutk\hello\HelloAlfa.java

package com.torutk.hello;

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 java.io.IOException;
import java.io.PrintWriter;
import java.time.ZonedDateTime;

@WebServlet(urlPatterns = {"/hello/alfa"})
public class HelloAlfa extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println(""" 
                <!DOCTYPE html>
                <html>
                <head>
                <meta charset="UTF-8">
                <title>Servlet/JSP Sample</title>
                </head>
                <body>
                <p>Hello!</p>
                <p>こんにちは!</p>
                <p>%s</p>
                </body>
                </html>
                """.formatted(ZonedDateTime.now()));
    }
}

サーブレットの実装は、HttpServletを継承します。

public class HelloAlfa extends HttpServlet {

このサーブレットは、URLパターン /hello/alfa でアクセスします。

@WebServlet(urlPatterns = {"/hello/alfa"})

HTTPのGETメソッドでアクセスするときは、HttpServletのdoGetメソッドをオーバーライドして処理を記述します。

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

引数には、リクエストに関する情報を保持するHttpServletRequestと、レスポンス(応答)を保持する(書き込む)HttpServletResponseを取ります。

レスポンス(response)に際して使用するMIMEタイプと文字コードを最初に(getWriter呼び出しより前に)指定します。

response.setContentType("text/html; charset=UTF-8");

応答メッセージを出力するwriterをresponse引数から取得します。

PrintWriter out = response.getWriter();

writerに、動的にHTMLを生成して文字列として渡します(println)。

        out.println(""" 
                <!DOCTYPE html>
                <html>
                <head>
                <meta charset="UTF-8">
                <title>Servlet/JSP Sample</title>
                </head>
                <body>
                <p>Hello!</p>
                <p>こんにちは!</p>
                <p>%s</p>
                </body>
                </html>
                """.formatted(ZonedDateTime.now()));

  • JDK 15で正式搭載されたテキストブロックで記述すると、Javaコード中にHTML文書を複数行まとめて書くことができます。
  • HTML文書の中で動的に決定する文字列(上述例では実行時の日時)は、%sプレースホルダーを使用して、formattedメソッドで指定します。
  • 実行時の日時は、Java Date Time APIのZonedDateTime(タイムゾーンを持つ日時クラス)で取得します。

warの再生成とtomcatへの配備

先ほどと同じくgradleでwarタスクを実行し、tomcat にwarファイルをコピーします。自動で展開・再ロードされますので、tomcatの再起動は不要です。

ブラウザでbookアプリケーションのサーブレットを表示

http://localhost:8080/book/hello/alfa

にWebブラウザでアクセスすると、サーブレットの実行結果が表示されます。

サーブレットプログラムの作成(web.xml使用)

web.xml で URLパターンを指定するサーブレット。
src\main\java\com\torutk\hello\HelloXray.java

HelloAlfa.javaとHelloXray.javaの差分を次に示します。

--- src/main/java/com/torutk/book/HelloAlfa.java    
+++ src/main/java/com/torutk/book/HelloXray.java    
@@ -1,7 +1,6 @@
 package com.torutk.book;

 import jakarta.servlet.ServletException;
-import jakarta.servlet.annotation.WebServlet;
 import jakarta.servlet.http.HttpServlet;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -9,8 +8,7 @@
 import java.io.IOException;
 import java.time.ZonedDateTime;

-@WebServlet(urlPatterns = {"/hello/alfa"})
-public class HelloAlfa extends HttpServlet {
+public class HelloXray extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         resp.setContentType("text/html; charset=UTF-8");

差分は次の2つです。

  • アノテーションでURLパターンを指定しないので削除
  • クラス名を変更

web.xmlにURLパターンを定義

  • src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" 
         version="6.0">
<servlet>
    <servlet-name>helloXray</servlet-name>
    <servlet-class>com.torutk.book.HelloXray</servlet-class>
</servlet>
 <servlet-mapping>
    <servlet-name>helloXray</servlet-name>
    <url-pattern>/hello/xray</url-pattern>
</servlet-mapping>
</web-app>
  • web-appタグは、お決まりの記述となります。
  • servletタグおよびservlet-mappingタグで、サーブレットのクラス名、URLパターンを定義します。
  • servlet-nameは、web.xmlファイル中で一意の名前であれば任意に記述可能です。通常はサーブレットのクラス名(パッケージ名なしの)にすると混乱ないでしょう。
  • web.xmlファイル中で、helloXrayの名前で指定するサーブレットは、FQCN(完全修飾クラス名)が com.torutk.book.HelloXrayである
    <servlet>
        <servlet-name>helloXray</servlet-name>
        <servlet-class>com.torutk.book.HelloXray</servlet-class>
    </servlet>
    
  • web.xmlファイル中でhelloXrayの名前で指定するサーブレットは、URLパターンが /hello/xray である
     <servlet-mapping>
        <servlet-name>helloXray</servlet-name>
        <url-pattern>/hello/xray</url-pattern>
    </servlet-mapping>
    

warファイルの生成

先ほどと同じくgradleでwarタスクを実行し、tomcat にwarファイルをコピーします。自動で展開・再ロードされますので、tomcatの再起動は不要です。
ここまでのプロジェクトで生成したbook.warの内容は次となります。

+-- META-INF/
|     +-- MANIFEST.MF
+-- WEB-INF/
|     +-- classes/
|     |     +-- com/
|     |           +-- torutk/
|     |                 +-- book/
|     |                       +-- HelloAlfa.class
|     |                       +-- HelloXray.class
|     +-- web.xml
+-- index.html

ブラウザでbookアプリケーションのサーブレットを表示

http://localhost:8080/book/hello/xray

にWebブラウザでアクセスすると、サーブレットの実行結果が表示されます。

JDBCドライバの配置

アプリケーションのwarファイルに、使用するJDBCドライバーを含めます。
WEB-INF/lib/h2-2.2.224.jar

Gradleの場合、warプラグインを使用し、依存性の定義に runtimeOnly でH2を記述すると WEB-INF/lib/の下にjarファイルが配置されます。

  • build.gradle.kts
    plugins {
        war
    }
        :
    dependencies {
        :
        runtimeOnly("com.h2database:h2:2.2.224")
    }
    

課題

CSSを使うには

HTMLからの使用

HTMLからcssファイルを適用するには、link要素でstylesheetを参照します。

  <head>
    <link rel="stylesheet" href="css/book.css">
  </head>

その際、Webアプリケーションとして構成するHTMLファイルは、コンテキストルートの下にコンテキストパスが入ります。そのため、href="/css/book.css" と記述するとCSSファイルが参照できません。
そこで、相対パスでCSSファイルを参照します。

開発プロジェクトでは次のようにcssファイルを配置します。

src
  +- main
      +- webapp
          +- css
          |   +- book.css
          +- hello.html

JSPからの使用

サーブレットからの使用


3日前に更新