Jetty Maven Pluginのscan（ホット再デプロイメント）を試してみる

# What's？

Jetty Maven Pluginを使うと、開発中にホット再デプロイメントができるらしいので試してみました。

# Jetty Maven Plugin

Jetty Maven Pluginのドキュメントはこちら。

[Using the Jetty Maven Plugin](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#jetty-maven-plugin)

ホット再デプロイメントについては、`jetty:run`ゴールや`jetty:run-war`ゴールで`scan`プロパティに値を設定すると有効にできます。

> **scan** 
> The pause in seconds between sweeps of the webapp to check for changes and automatically hot redeploy if any are detected. By default this is -1, which disables hot redeployment scanning. A value of 0 means no hot redeployment is done, and that you must use the Enter key to manually force a redeploy. Any positive integer will enable hot redeployment, using the number as the sweep interval in seconds.

- [jetty:run](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#jetty-run-goal)
- [jetty:run-war](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#jetty-run-war-goal)

デフォルトは`-1`で無効化されていて、`0`を指定するとEnterキーで実行させることになります。  
正の整数を指定すると、その間隔でホット再デプロイメント対象をスキャンするようです。

スキャン対象は、`jetty:run`ゴールの場合は以下のディレクトリです。

- resources in `${project.basedir}/src/main/webapp`
- classes in `${project.build.outputDirectory}`
- web.xml in `${project.basedir}/src/main/webapp/WEB-INF/`

`jetty:run-war`の場合はWARファイルですね。

今回は`jetty:run`で試してみたいと思います。

# 環境

今回の環境はこちら。

```shell
$ java --version
openjdk 17.0.10 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-122.04.1)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-122.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 17.0.10, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-101-generic", arch: "amd64", family: "unix"
```

# サンプルアプリケーション

Jerseyを使って、簡単なJakarta RESTful Web Services（JAX-RS）アプリケーションを書いて試すことにします。

```:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>jetty-reload-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>3.1.5</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>3.1.5</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty.ee10</groupId>
                <artifactId>jetty-ee10-maven-plugin</artifactId>
                <version>12.0.7</version>
            </plugin>
        </plugins>
    </build>
</project>
```

```java:src/main/java/org/example/web/ApplicationConfig.java
package org.example.web;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

import java.util.Set;

@ApplicationPath("")
public class ApplicationConfig extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(HelloResource.class);
    }
}
```

```java:src/main/java/org/example/web/HelloResource.java
package org.example.web;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("hello")
public class HelloResource {
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String message() {
        return "Hello World";
    }
}
```

まずは動作確認。Jettyを起動。

```shell
$ mvn jetty:run
```

確認。

```shell
$ curl localhost:8080/hello
Hello World
```

ここからは、Jetty Maven Pluginの`scan`の設定と

```xml
            <plugin>
                <groupId>org.eclipse.jetty.ee10</groupId>
                <artifactId>jetty-ee10-maven-plugin</artifactId>
                <version>12.0.7</version>
                <configuration>
                    <scan>...</scan>
                </configuration>
            </plugin>
```

以下のメッセージの部分を変えて確認してみましょう。

```java
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String message() {
        return "Hello World";
    }
```

# Jetty Maven Pluginのホット再デプロイメントを試す

では、Jetty Maven Pluginのホット再デプロイメントを試してみます。

ここからは、`jetty:runPゴールで起動中に

```shell
$ mvn jetty:run
```

以下の部分を

```java
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String message() {
        return "Hello World";
    }
```

メッセージの末尾に`!!`を追加して確認しているものとします。

```java
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String message() {
        return "Hello World!!";
    }
```

## デフォルト（無効化）

まずはデフォルトから。`scan`を`-1`にしている状態と同義です。

```xml
            <plugin>
                <groupId>org.eclipse.jetty.ee10</groupId>
                <artifactId>jetty-ee10-maven-plugin</artifactId>
                <version>12.0.7</version>
            </plugin>
```

この場合、起動時にホット再デプロイメントが無効化されていることがログ出力されます。

```shell
[INFO] Automatic redeployment disabled, see 'mvn jetty:help' for more redeployment options
```

なので、コンパイルしても

```shell
$ mvn compile
```

変更は反映されません。

```shell
$ curl localhost:8080/hello
Hello World
```

## scanを0に設定する

次は、`scan`を`0`に設定します。

```xml
            <plugin>
                <groupId>org.eclipse.jetty.ee10</groupId>
                <artifactId>jetty-ee10-maven-plugin</artifactId>
                <version>12.0.7</version>
                <configuration>
                    <scan>0</scan>
                </configuration>
            </plugin>
```

これで`jetty:run`で起動すると、ログにEnterで再デプロイメントが行われることが出力されます。

```shell
Hit <enter> to redeploy:

```

ここで、ソースコード変更の後にコンパイルして

```shell
$ mvn compile
```

`jetty:run`を実行しているターミナルでEnterを押すと再デプロイメントが行われます。

```shell
[INFO] Restarting oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Classes = /path/to/jetty-reload-example/target/classes
[INFO] Stopped oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STOPPED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Context path = /
[INFO] Tmp directory = /path/to/jetty-reload-example/target/tmp
[INFO] web.xml file = null
[INFO] Webapp directory = /path/to/jetty-reload-example/target/webapp-synth
[INFO] Web defaults = org/eclipse/jetty/ee10/webapp/webdefault-ee10.xml
[INFO] Web overrides =  none
[INFO] Started oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
3月 28, 2024 1:03:47 午後 org.glassfish.jersey.message.internal.MessagingBinders$EnabledProvidersBinder bindToBinder
警告: A class jakarta.activation.DataSource for a default provider MessageBodyWriter<jakarta.activation.DataSource> was not found. The provider is not available.
3月 28, 2024 1:03:47 午後 org.glassfish.jersey.server.wadl.WadlFeature configure
警告: JAX-B API not found . WADL feature is disabled.
[INFO] Started oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Restart completed at Thu Mar 28 13:03:47 JST 2024
```

変更が反映されました。

```shell
$ curl localhost:8080/hello
Hello World!!
```

コンパイルが必要なのは、スキャン対象が以下だからですね。

- resources in `${project.basedir}/src/main/webapp`
- classes in `${project.build.outputDirectory}`
- web.xml in `${project.basedir}/src/main/webapp/WEB-INF/`

## scanを正の整数に設定する

最後は、`scan`を正の整数に設定してみます。今回は5秒にしてみました。

```xml
            <plugin>
                <groupId>org.eclipse.jetty.ee10</groupId>
                <artifactId>jetty-ee10-maven-plugin</artifactId>
                <version>12.0.7</version>
                <configuration>
                    <scan>5</scan>
                </configuration>
            </plugin>
```

`jetty:run`で起動すると、5秒に1度スキャンすることを示すログが出力されます。

```shell
[INFO] Scan interval sec = 5
```

ここでソースコードを変更し、先ほどと同様コンパイルすると

```shell
$ mvn compile
```

今度は自動検出して再デプロイメントが行われます。この間隔を`scan`で指定するわけですね。

```shell
[INFO] Restarting oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Classes = /path/to/jetty-reload-example/target/classes
[INFO] Stopped oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STOPPED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Context path = /
[INFO] Tmp directory = /path/to/jetty-reload-example/target/tmp
[INFO] web.xml file = null
[INFO] Webapp directory = /path/to/jetty-reload-example/target/webapp-synth
[INFO] Web defaults = org/eclipse/jetty/ee10/webapp/webdefault-ee10.xml
[INFO] Web overrides =  none
[INFO] Started oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Started oeje10mp.MavenWebAppContext@dbed7fd{ROOT,/,b=file:///path/to/jetty-reload-example/target/webapp-synth/,a=AVAILABLE,h=oeje10s.SessionHandler@113dcaf8{STARTED}}{file:///path/to/jetty-reload-example/target/webapp-synth/}
[INFO] Restart completed at Thu Mar 28 13:08:46 JST 2024
```

結果は、0秒を指定した時と同じなので割愛します。