ServletのWebアプリをJakarta対応する話（１）

Java の Servletで書かれれたWebアプリをメンテしています。
30画面ほどあります。個人開発（自分１人で開発）の社内用Webアプリです。
これをなるべく、手間をかけずに最新のTomcatやWildflyに対応させる話です。


# 旧アプリ（現状）
・Tomcat 9 と Java11で動いています。
・Servlet APIで書かれています。 2000年頃のサーブレットの書き方です。
・  最近のサーブレットの書き方（アノテーション）は使用していません。
・Servletはweb.xmlに１個ずつ登録しています。 servletタグと servlet-mappingタグを使って、登録しています。
・表示は JSP のみ。タグリブも使っていない。
・DBアクセスにはMyBatisを使っています。
・ロギングは slf4j-api, logback-core, logback-classicを使っています。
・IDEはNetBeansを使い、antでビルドして、Tomcatに入れています。
・開発はWindowsPC or Macで行い、AWS上のLinuxにデプロイします。
・なおこのAWSのサーバーの前には、KeyCloakとリバースプロキシを置いて認証はそちらに任せています。

なんでこんな技術なのか：
言い訳のようになりますが、重厚長大なフレームワークは個人開発では厳しいのです。問題が発生した際に聞く人がいないからです。中小企業の社内開発なので、仕方ないです。


# 書き換え後（書き換え中なので希望）
・Tomcat 10または最新のJakartaEEで動かしたい
・既存のソースの書き替えはなるべく少なくしたい
・当面 Java11のまま。半年後をめどに Java17に移行したい
・ビルドツールは ant から mavenに変更 (mavenは苦手なのですが、JakartaEE対応するには、mavenは必須かとあきらめました。

# 候補となるアプリサーバー
・Tomcat 10は候補として残しておく。現状Tomcat 9なので安心。
・JakartaEEのサーバーは多数ありますが、認証にKeyCloakを使っている事から、そのアプリサーバーは、勉強してもいいなかーと思いました。QuarkusとWildflyです。
・それ以外のアプリサーバーもいくつかインストールだけしてみたのですが、特に気に入ったものはありませんでした。
・Quarkusは、少しアプリも作って見ました。非常に面白かったのですが、上記に書いてある既存のServletアプリを移植するには難しそうな印象を持ちました。アノテーションを使った方法などに、書き換えていけば動くのかも知れませんが、工数がどれくらいかかるか見通せません。
・最終的に Tomcat 10 か WildFly 28.0.1 , 27.0.1 あたりにしたいと思いました。
・WildFlyで動く事が確認できれば、機能が多いので、それにするつもりです。(Tomcat 10は保険）

# IDEはどうするか？
・検索した所、eclipse と wildflyで開発している記事をいくつか見つけました。
　　それもトライしてみたのですが、最新のWildFlyではうまく動きませんでした。
・eclipseプラグインは WildFly 24あたりでないと対応していない様子でした。
・eclipseと、プラグインで開発するのはあきらめました。

# Wildflyのインストールとサンプル

![wildfly.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339875/634541dd-0256-2ce5-6caa-a41e55c39a71.png)

ダウンロードして、展開するだけなので、特に難しい事はありませんでした。

# 既存ソースの書き換え

import文の javax.servletを jakarta.servletに書き換えるだけです。
３０画面ほどあるので、３時間ほどかかりましたが、単純作業でした。

# mavenのpomをどうするか

Tomcat 10用のpom.xmlも検索すればあったのですが、結局 Wildflyのサンプルを参考にして作る事にしました。

helloworldか、 bmtあたりを参考にして作りました

```pom.xml
<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>com.foobar</groupId>
  <artifactId>skill-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>skill-app Maven Webapp</name>
  <!-- FIXME change it to the project&amp;apos;s website -->
  <url>http://www.example.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- The versions for BOMs, Dependencies and Plugins -->
    <version.server.bom>28.0.1.Final</version.server.bom>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <profiles>
    <profile>
      <id>windows</id>
      <build>
        <resources>
          <resource>
            <directory>src/windows/resources</directory>
          </resource>
          <resource>
            <directory>src/common/resources</directory>
          </resource>
        </resources>
      </build>
    </profile>
    <profile>
      <id>mac</id>
      <build>
        <resources>
          <resource>
            <directory>src/mac/resources</directory>
          </resource>
          <resource>
            <directory>src/common/resources</directory>
          </resource>
        </resources>
      </build>
    </profile>
  </profiles>

  <repositories>
    <repository>
      <id>jboss-public-maven-repository</id>
      <name>JBoss Public Maven Repository</name>
      <url>https://repository.jboss.org/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
      </releases>
      <snapshots>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
      </snapshots>
      <layout>default</layout>
    </repository>
    <repository>
      <id>redhat-ga-maven-repository</id>
      <name>Red Hat GA Maven Repository</name>
      <url>https://maven.repository.redhat.com/ga/</url>
      <releases>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
      </releases>
      <snapshots>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
      </snapshots>
      <layout>default</layout>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>jboss-public-maven-repository</id>
      <name>JBoss Public Maven Repository</name>
      <url>https://repository.jboss.org/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
    <pluginRepository>
      <id>redhat-ga-maven-repository</id>
      <name>Red Hat GA Maven Repository</name>
      <url>https://maven.repository.redhat.com/ga/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
  <dependencyManagement>
    <dependencies>
      <!-- importing the jakartaee8-with-tools BOM adds specs and other useful artifacts as managed dependencies -->
      <dependency>
        <groupId>org.wildfly.bom</groupId>
        <artifactId>wildfly-ee-with-tools</artifactId>
        <version>${version.server.bom}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>


  <dependencies>
    <!-- Import the CDI API, we use provided scope as the API is included in JBoss EAP -->
    <dependency>
      <groupId>jakarta.enterprise</groupId>
      <artifactId>jakarta.enterprise.cdi-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <!-- Import the Common Annotations API (JSR-250), we use provided scope  as the API is included in JBoss EAP -->
    <dependency>
      <groupId>jakarta.annotation</groupId>
      <artifactId>jakarta.annotation-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <!-- Import the EJB API, we use provided scope as the API is included in JBoss EAP -->
    <dependency>
      <groupId>jakarta.ejb</groupId>
      <artifactId>jakarta.ejb-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <!-- Import the Servlet API, we use provided scope as the API is included in JBoss EAP -->
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.10.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.4</version>
    </dependency>
  </dependencies>
  <!--
  mvn compile war:exploded -P windows
  
  -->
  <build>
    <finalName>skill</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.2</version>
        <configuration>
          <!-- webappDirectory&amp;gt;C:/usr2/test/skill.war&lt;/webappDirectory -->
          <webappDirectory>C:/usr2/wildfly-28.0.1.Final/wildfly-28.0.1.Final/standalone/deployments/skill.war</webappDirectory>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

```

:::note info
mvn clean
mvn package  -P windows
:::

target に skill.war が作成されます。(skillというのは、作成しているアプリ名です)


:::note warn
profiles の部分は、WindowsとMacを切り替えるために使っているだけです。
不要なら削除してください
:::


# WildFlyに入れてみる
WildFlyのインストールDIR/standalone/deploymentsに上記のwarを入れ
WildFlyのインストールDIR/bin/standalone.batで起動すると
それなりに動作しました。

:::note warn
logback-core, logback-classicは WildFlyでは使えないそうなので、
上記のpom.xmlからは削除しました。
WildFlyのログに混ざって出力されるそうです。slf4j-apiは、入れておく必要があります。
:::

# Tomcat10 に入れてみる
Tomcat10のインストールDIR/webappsに上記のwarを入れて、startup.batで起動するとそれなりに動きました。

:::note warn
logback-core, logback-classicは Tomcat 10には入れる必要があります。
maven初心者でやり方が、わからないため、antでこの処理を行っています。
:::

# 修正箇所（現時点でのまとめ）
・上述の   javax.servletを jakarta.servletに変更する
・上述の  logback-core, logback-classicの問題
・WEB-INF ディレクトリに置いたプロパティファイルが、なぜか、mac + wildflyの時だけ、うまく動きませんでした。

```sample.java
private Properties getConfigProperties(HttpServlet servlet) throws IOException {

        Properties menuProp = new Properties();
        try (InputStream is = servlet.getServletContext().getResourceAsStream("WEB-INF/sample.properties")) {
            
            menuProp.load(new InputStreamReader(is, "UTF-8"));

        }
        return menuProp;
}

```

servlet.getServletContext().getResourceAsStream()がうまく、動かない様子です。
理由はわかりません。下のように書き替えました。


```sample.java (修正)
Properties configProp = new Properties();

InputStream is = I18n.class.getResourceAsStream(path);
if (is == null) {
      LOG.error("prop file not found. {}", path   );
} else {
      try {
          configProp.load(new InputStreamReader(is, "UTF-8"));
      } finally {
          is.close();
      }
}
```





# web.xmlについて

```web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  
<servlet>
  <servlet-name>MenuServlet</servlet-name>
  <servlet-class>zz.skill.serv.MenuServlet</servlet-class>
</servlet>

途中省略

<servlet-mapping>
  <servlet-name>MenuServlet</servlet-name>
  <url-pattern>/gu/Menu</url-pattern>
</servlet-mapping>

途中省略

<filter>
    <filter-name>oidcEmu</filter-name>
    <filter-class>zz.filter.OidcDevFilter</filter-class>
</filter>

<filter-mapping> 
    <filter-name>oidcEmu</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 

</web-app>
```

先頭の DOCTYPE宣言は最新のJakartaEEだと変わるそうですが、 Tomcat 9時代に使っていたものを、そのまま変更なしで、動きました。


# WildFlyのHotデプロイについて

以下の記事を参考にさせてもらいました。(参考1)

https://qiita.com/musucateko/items/a7ba1dd5fb1252fd5efc

インストールディレクトリ/standalone/configration/ 以下にある、以下のファイルを編集します。
赤枠部分を追加しました。

![hotdeploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339875/ec9bf2a4-7b5b-3175-0100-7c1dd6b387da.png)



デプロイについては、以下の記事を参考にさせてもらいました。(参考2)

https://qiita.com/tama1/items/a4479cbd5b15d8c4677e


一度、deploymentsディレクトリに warファイルを置いて standalone.batで起動すると
XXX.war.deployedファイルというのが出来ます。

![deployed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339875/0084b44c-a7a6-419d-35ba-2c699c532b23.png)


上手の画面キャプチャが合っていなくて、申し訳ないのですが、前述の pom.xmlで作成した
skill.war　を置いて、起動した際に、 skill.war.deployedというファイルが出来ます。

この状態で、一度 WildFly を停止します。 Cntrol-Cで、停止。

その状態で、skill.warを削除します。 skill.war.deployedは残しておきます。
そして、エクスプローラーで、 skill.warというフォルダーを作成します。

(参考２)の記事にある
展開形式
zipを展開したWEBアプリケーションです。展開したディレクトリを「xxx.war」のような名前にしたものです。
の事です。

そして、このディレクトリに、mavenの出力を出力できればいいのですが・・

mavenのマニュアルにその方法がありました（参考３）

https://maven.apache.org/plugins/maven-war-plugin/usage.html

![maven.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339875/1d180b36-a78a-1286-8d51-0fac781c477b.png)


webappDirectoryのタグの所に前述で作成した skill.warというフォルダを指定します。


:::note info
mvn clean
mvn compile war:exploded  -P windows
:::

で、その位置に、warが展開された形で、コンパイルされる事を確認できました。

# Hotデプロイを使ってみたところ

DOS窓を２つ立ち上げます。
１つは、WildFlyのディレクトリ/bin でstandalone.batを起動。
もう１つはソースがあるディレクトリの pom.xmlがある位置で開く。

JavaやJSPの編集は、お好きな IDEやエディタで編集。

編集が終わったら、以下を実行。

:::note info
mvn compile war:exploded  -P windows
:::

WildFlyの設定ファイルには、５秒おきに、変更を監視とありますので、５秒ほど経過すると、変更を検知して自動的に読み込んでくれました。

但し、何回か実行を繰り返してみたところ、たまに、mavenが失敗する事がありました。
どうもWildFlyが変更を検知して読み込んでいる途中では、ファイルにロックがかかる様子です。
mavenがそのファイルは別のプロセスで使っているので、書き込めませんというようなエラーが出てしまいました。

とりあえず、開発が出来そうな所までは、行きました。
しばらくWildFlyで遊んでみて、うまく行きそうなら、それにします。
ダメならTomcat 10にするというつもりです。

以上。　最後まで読んでいただき、ありがとうございました。