EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Tomcat8のjar読み込み順について

結論

  • Tomcat8ではjarの読み込み順が決まっていない

Tomcat7以前のようにアルファベット順に読み込まれると思うと痛い目に遭います。

きっかけ

昨年末からサーバの増強を進めています。サーバの構築は約一年ぶりなので不安も有りましたが、以前作成したAnsibleのplaybookを利用することで問題なく作業も完了しました。 サーバの動作確認も問題無く終え、Webアプリの移行を行い本番環境に投入しました。 ところが、移行したTomcat8で動くアプリの特定のページでエラーが発生しているという報告を受けました。 実際にアクセスしてエラーが発生していることを確認し、エラーログを確認すると該当ページで"java.lang.NoSuchMethodError"が発生していました。

java.lang.NoSuchMethodError発生の原因

このエラーは、クラスにメソッドの定義がない場合に発生します。このエラー自体は知っていましたが、あまりお目にかからないものです。 メソッドの定義が無い場合、該当ページのクラスをビルドするタイミングで発見されるハズです。実行時にエラーが発生するということが不思議です。 もっと不思議なのは、そもそもこのアプリは別のサーバでは正常に動くのです。 気になるのが、該当のメソッドが定義されているクラスが複数のjarに存在することです。

xxx.yyy.Zzz

このようなクラスが aaa.jar と bbb.jar に存在し、該当メソッドは aaa.jar に含まれるクラスにのみ定義されていました。 長年Tomcatを利用したWebアプリを開発している経験では、名前の順で上になる aaa.jar のクラスが利用されるという認識です。 今回も当然 aaa.jar のクラスが利用されていると考えたのですが、念の為クラスが含まれるjarをログに出力させたところ、 bbb.jar となっていました。

jarの読み込み順

Tomcatのクラスローダでは、Webアプリでの優先度が以下のようになります。

  1. WEB-INF/classes
  2. WEB-INF/lib

この挙動を利用して、優先させたいクラスやプロパティファイルを WEB-INF/classes に配置することもありました。 WEB-INF/lib は、上で書いたようにアルファベット順に読み込まれるので、jarのファイル名を変更したこともありました。 ですが、その理解が間違っていたようです。検索していたところ、このような書き込みがありました。

https://stackoverflow.com/questions/5474765/order-of-loading-jar-files-from-lib-directory

Order for loaded jars in WEb-INF/lib folder.

For tomcat 5-7 the order is alphabetical. It uses sort.

For tomcat 8 is random decided by underlying file system.

Tomcat8から、ファイルシステムに依存するとあります。これを確認するため、ログにクラスローダが読み込んだjarを出力させてみました。

org.apache.catalina.loader.WebappClassLoader {context-class-loader=true, id=1539094878}
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/classes/
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/lib/bbb.jar
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/lib/eee.jar
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/lib/ccc.jar
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/lib/aaa.jar
    file:/usr/local/tomcat/webapps/mognavi-backend/WEB-INF/lib/ddd.jar

実際とは異なりますが、上記のように aaa.jar が bbb.jar より後に読み込まれていました。このため、メソッドの定義がないためエラーが発生していました。 ちなみに、移行前の環境でも同様のログを出力させたところ、 aaa.jar が bbb.jar より先に読み込まれていました。このため、いままこの問題に気付かなかったようです。

解決

解決方法としては、利用しない bbb.jar に該当するクラスを含まないようにビルドすることにしました。(bbb.jar は社内で制作したライブラリのため、この方法を採ることが出来ました) PreResourcesを利用する、ファイルシステムを変更する、など他の手段もあるようでしたが、そもそも該当クラスが利用されていないことと今後も同様のトラブルが他で発生する可能性があるので、このような対処をとりました。 これらはまた別の機会に調査したいと思います。