プロジェクト

全般

プロフィール

Gradle warプロジェクト作成

はじめに

Gradleにおいて、warプラグインを適用し、サーブレットアプリケーションをビルドするGradleプロジェクトを作成します。
ただし、gradleのinitタスクには war 用のオプションが存在しないので、別な手段で作成します。

手順

単一モジュールのwar作成プロジェクトをいちから作成

準備

  • プロジェクトのディレクトリを用意します。
work % mkdir helloWar
work % cd helloWar
helloWar % 
  • settings.gradle.kts ファイルを新規に作成し、プロジェクト名を記述します。
rootProject.name = "helloWar" 

この時点でのプロジェクトディレクトリ

helloWar
  +-- settings.gradle.kts
  • wrapperを実行
helloWar % gradle wrapper
  :

この時点のディレクトリ

helloWar
  +-- .gradle
  +-- gradle
  |     +-- wrapper
  +-- gradlew
  +-- gradlew.bat
  +-- settings.gradle.kts

build.gradle.kts の編集

build.gradle.ktsを作成し、warプラグインを使うビルドの定義を記述していきます。

plugins {
    war
}

この時点のディレクトリ

helloWar
  +-- .gradle
  +-- build.gradle.kts
  +-- gradle
  |     +-- wrapper
  +-- gradlew
  +-- gradlew.bat
  +-- settings.gradle.kts

ソースファイルやWeb定義ファイルがないですが、ビルドが可能です。

helloWar % gradle wrapper
  :

ビルド結果が、buildディレクトリ以下に生成されます。warファイル名は、プロジェクト名に.warを付けた名前です。

helloWar
  +-- build
        +-- libs
        |     +-- helloWar.war
        +-- tmp

helloWar.warの中は、MANIFEST.MFだけが入った状態です。

  • サーブレットAPIのライブラリを指定

Mavenリポジトリから、サーブレットAPIを取得しコンパイル時に使用するよう定義します。

repositories {
    mavenCentral()
}

dependencies {
    compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0")
}
  • ソースファイル、リソースファイル、Webコンテンツファイルなどを収容するディレクトリを生成します。
ソースディレクトリ warファイル内への配置 備考
src/main/java WEB-INF/classes コンパイルされたクラスファイルをwarに配置
src/main/resources WEB-Inf/
src/main/webapp ルート直下 静的コンテンツなど
helloWar % mkdir -p src/main/{java,resources,webapp}
helloWar % ls src/main        
java      resources webapp

静的コンテンツの作成とwarファイル生成

まずは、index.htmlを作成します。

  • src/main/webapp/index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Gradle web applicaiton samples</title>
    </head>
    <body>
        <p>Hello, Gradle War World.</p>
    </body>
</html>

ビルドしてwarファイルを作成します。

helloWar % ./gradlew war

BUILD SUCCESSFUL in 717ms
1 actionable task: 1 executed

helloWar % ls build/libs 
helloWar.war

helloWar % jar tf build/libs/helloWar.war 
META-INF/
META-INF/MANIFEST.MF
index.html

生成されたwarファイルは、プロジェクト名 helloWar に拡張子.warをつけた helloWar.warとなりました。
src/main/webapp/index.htmlがwarファイルのルートに配置されています。

warファイルをサーブレットコンテナに配置し実行

tomcatのwebappディレクトリ下に、helloWar.warファイルをコピーし、tomcatを起動します。
以下は、MacOSにhomebrewでインストールしたtomcat 10.1.19にコピーして実行する例です。

helloWar % cp build/libs/helloWar.war /opt/homebrew/opt/tomcat/libexec/webapp/
helloWar % catalina run
  :
31-Mar-2024 21:12:05.386 情報 [main] org.apache.catalina.startup.HostConfig.deployWAR Webアプリケーションアーカイブ [/opt/homebrew/Cellar/tomcat/10.1.19/libexec/webapps/helloWar.war] を配備します
31-Mar-2024 21:12:05.560 情報 [main] org.apache.catalina.startup.HostConfig.deployWAR Web アプリケーションアーカイブ [/opt/homebrew/Cellar/tomcat/10.1.19/libexec/webapps/helloWar.war] の配備は [171] ミリ秒で完了しました
  :

配置したhelloWar.warファイルがアプリケーションとして配備されたメッセージが表示されました。
tomcatでは、配置したwarファイルの名前(helloWar)がコンテキストパスとして使用されます。
そこで、ブラウザから http://localhost:8080/helloWar/ にアクセスすると、index.htmlが表示されます。

サーブレットソースコードの作成

src/main/java の下に、パッケージ名に基づくディレクトリ階層でサーブレットのソースファイルを作成します。

  • src/main/java/com/torutk/hello/HelloServlet.java
package com.torutk.hello;

import java.io.IOException;
import java.io.PrintWriter;
import java.time.ZonedDateTime;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {

    @Override
    public void doGet(
        HttpServletRequest request, HttpServletResponse response
    ) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("Hello, Gradle War World.");
        out.println(ZonedDateTime.now());
    }

}

サーブレットのソースコードは、コンパイルされてwarファイルのWEB-INF/classes以下にパッケージ階層に応じたディレクトリにクラスファイルが配置されます。

helloWar % jar tf build/libs/helloWar.war
META-INF/
META-INF/MANIFEST.MF
WEB-INF/
WEB-INF/classes/
WEB-INF/classes/com/
WEB-INF/classes/com/torutk/
WEB-INF/classes/com/torutk/hello/
WEB-INF/classes/com/torutk/hello/HelloServlet.class
index.html

アプリケーション設定の記述

サーブレットコンテナに配置したサーブレットクラスへアクセスするURLのパスを定義する必要があります。
サーブレットクラスにアノテーションでパスを定義する方法と、web.xmlにパスを定義しサーブレットコンテナに配置する方法の2つがあります。
ここでは、web.xml に定義を記述します。

  • src/main/webapp/WEB-INF/web.xml

お決まりの記述(schema, 名前空間の定義)

<?xml version="1.0" encoding="UTC-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">
</web-app>

サーブレット毎の記述

  • (サーブレット名、URLパターン)の定義
  • (サーブレット名、サーブレットクラス名)の定義

ここでは、サーブレット名 HelloServlet、URLパターン名 /hello、サーブレットクラス名 com.torutk.hello.HelloServletとします。

<?xml version="1.0" encoding="UTC-8"?>
<web-app ...略>
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.torutk.hello.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

ローカルマシンでtomcat 10.1を動かして、tomcat の webappsディレクトリに helloWar.war を配置した場合、
http://localhost:8080/helloWar/hello をアクセスすると、サーブレットが実行されます。

web.xmlの定義では、次を記述します。

  • <servlet-name> 定義対象のサーブレットをweb.xmlの設定上で識別する名前。実装したサーブレットのクラス名にする遠い
  • <servlet-class> 定義対象のサーブレットのFQCN(完全修飾クラス名)を記述
  • <url-pattern> 定義対象のサーブレットを呼び出すURLのコンテキストルート以下
http://サーバーDNS名:ポート番号/コンテキストパス/URLパターン

コンテキストパスは、tomcatの場合 webappsディレクトリ下に配置したアプリケーションのディレクトリ名(warファイルを配置した場合、warファイルの拡張子なしの名前のディレクトリが作られる)となります。

コンテキストパスを変更する場合は、warファイルのMETA-INF/context.xml に定義するようですが、tomcatでwarファイルの自動展開をすると、重複が発生してあまりうまくないようです。

バージョンの導入

一般的なアプリケーション・ライブラリの成果物(JARファイル等)は、ファイル名にバージョン識別子を含めることが多いです。例えば、myapp-0.15.3.jar のように。
しかし、Webアプリケーションの成果物(WARファイル)は、拡張子.warを除いたファイル名がコンテキストパスに使われることが多いようで、その場合バージョン識別子を含んだwarファイル(例、myapp-2.1.war)を提供してしまうと、WebアプリケーションにアクセスするURLにバージョンが含まれ、バージョン毎に異なってしまうという問題になります。例えば、https://the.server.example.com/myapp-2.1/hello となり、バージョンアップすると https://the.server.example.com/myapp-2.2.1/hello とURLが変わってしまいます。

対応はちょっと厄介で、アプリケーションサーバー共通の設定ファイルに、warファイルの配備の度にマッピングを書き換えて再起動するなどの処置が発生します。

  • build.gradle.kts
version="1.0-SNAPSHOT" 
  • 生成されるwarファイル名にバージョン文字列が追加されます。
    • helloWar-1.0-SNAPSHOT.war

warファイル名が変わると、URL(コンテキストパス)が変わってしまいます。

http://localhost:8080/helloWar-1.0-SNAPSHOT/hello

tomcat固有の解決法であれば、warファイルの名前を、コンテキストパス##バージョン.war(例:helloWar##1.0-SNAPSHOT.war)とすると、コンテキストパスは##の前の文字列となり、バージョンが##の後の文字列となります。

version="1.0-SNAPSHOT" 

tasks.war {
    archiveFileName = "helloWar##$version.war" 
}

tomcatのwebappsディレクトリには、##が付いたファイルとディレクトリが展開されますが、コンテキストパスは##の前の名前となります。

webapps % ls -1
  :
helloWar##1.0-SNAPSHOT
helloWar##1.0-SNAPSHOT.war
  :

メモ

warファイルに含めたいファイル

WEB-INF/lib下にjarファイル

  • warプラグインは、dependenciesでruntimeに指定されたライブラリを、warファイルのWEB-INF/lib下に含める
  • 注)providedCompileおよびprovidedRuntimeで指定したライブラリは、アプリケーションサーバーで用意されるライブラリを指定するもので、warファイルには含まれない


23日前に更新