確認環境
JBoss EAP 6.4.0, 7.0.0, 7.1.0beta1
モジュールとライブラリの競合が起こる原因
Java EEアプリケーションサーバとしてJBoss EAP(以下JBoss)を使う際の注意点としてライブラリとモジュールの競合があります。
詳細については、開発ガイドの3章クラスローディングとモジュールに記載されていますが、簡潔に説明すると、JBossではアプリケーションのライブラリよりもJBossに同梱されているモジュールが優先してロードされます。(ざっと見た感じ順番を変える設定はなさそうです。)
つまり、アプリケーションがビルド時に使用したライブラリのバージョンと異なるバージョンで実行される可能性があり、起動時や実行時にエラーが発生するかもしれません。(今は大丈夫でもバージョンアップしたときに問題が起こるかも)
ロードされるモジュールを把握する
JBossのモジュールはデプロイしたものによって自動的にロードされるので、動かしたいアプリケーションを1回デプロイしてみて、何がロードされるか把握しましょう。
{JBOSS_HOME}/standalone/configuration
にあるstandalone.xml
のデフォだと115行目くらいにログ出力に関する設定があるので、levelをDEBUGに変更します。
<root-logger>
<level name="DEBUG"/>
<handlers>
<handler name="CONSOLE"/>
<handler name="FILE"/>
</handlers>
</root-logger>
この状態でJBossを起動して何かアプリケーションをデプロイしてみてください。
すると{JBOSS_HOME}/standalone/server.log
にアプリケーションのライブラリとJBossがロードしたモジュールの一覧が出力されています。
Adding resourceと書かれている部分がwarから読み込まれたライブラリで、
Adding dependency ModuleDependencyと書かれている部分がロードされたモジュールです。
以下は出力例です。
2017-09-04 18:29:42,597 DEBUG [org.jboss.as.server.deployment] (MSC service thread 1-7) Adding resource "/C:/jboss/standalone/deployments/todo.war/WEB-INF/lib/aopalliance-1.0.jar" to module deployment.todo.war
2017-09-04 18:29:42,648 DEBUG [org.jboss.as.server.deployment] (MSC service thread 1-7) Adding dependency ModuleDependency [identifier=javax.ejb.api, moduleLoader=local module loader @282ba1e (finder: local module finder @13b6d03 (roots: C:\jboss\modules,C:\jboss\modules\system\layers\base)), export=false, optional=false, importServices=true] to module deployment.todo.war
今回は例としてjavax.ejb.api
を除外してみます。
モジュールに関する設定ファイルの作成
WebアプリケーションのWEB-INF直下にjboss-deployment-structure.xml
を配置します。
配置したら以下のように設定します。
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">
<deployment>
<exclusions>
<module name="javax.ejb.api" />
</exclusions>
</deployment>
</jboss-deployment-structure>
除外するモジュールを<exclusions>
で囲うだけです。
パスから指定する必要がありますが、これは各モジュールのmodule.xml
に書かれています。
では、この状態でJBossを再起動してみます。
ログが長いので割愛しますが、Adding dependency ModuleDependencyの欄にjavax.ejb.api
がないはずです。
その他の設定
jboss-deployment-structure.xml
には、もともとJBossに含まれないモジュールを追加したり、複数のモジュールで構成されるサブシステムごと除外したりできます。ここでは取り上げませんが、設定方法が開発ガイドの3章に書かれていますので参考にしてください。
この投稿に至った経緯
Tomcatで問題なく動くアプリケーションがJBossだとエラーになる原因がわからず四苦八苦したので
おまけ
具体的な事例
こっちをメインに書くか悩んだのですが、単にモジュールを除外設定すればいいという話ではなかったので、おまけとして……
TERASOLUNA(5.3.0)のブランクプロジェクトを作成し、HelloController.java
に以下のコードを追加します。
/** (略 **/
import com.fasterxml.jackson.databind.util.StdDateFormat;
/** (略 **/
@RequestMapping(value = "/", method = { RequestMethod.GET, RequestMethod.POST })
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
// ここから
Set<DateFormat> set = new HashSet<>();
set.add(StdDateFormat.instance);
// ここまで
model.addAttribute("serverTime", formattedDate);
return "welcome/home";
}
}
これをSTS同梱のPivotal tc Server(v3.2)にデプロイしてhttp://localhost:8080/todoにアクセスしてみます。
特に問題はありませんね。
では次にJBoss EAP 7.0.0にデプロイしてアクセスしてみましょう。
なんかエラーになりました。コンソールにもエラーが出ています。
15:28:42,923 ERROR [org.terasoluna.gfw.common.exception.ExceptionLogger] (default task-2) [e.xx.fw.9001] UNDEFINED-MESSAGE: java.lang.NullPointerException
at java.text.DateFormat.hashCode(Unknown Source)
at java.util.HashMap.hash(Unknown Source)
at java.util.HashMap.put(Unknown Source)
at java.util.HashSet.add(Unknown Source)
at todo.app.welcome.HelloController.home(HelloController.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
(略)
HelloController.java
の38行目はset.add(StdDateFormat.instance);
の部分です。
このエラーは報告されていて、jackson-databindの2.7.2で既に修正されています。
TERASOLUNA 5.3.0が利用するjackson-databindのバージョンはスタック一覧にあるように、2.8.5なのですが、なぜこのような問題が起こったのでしょうか?
それは、JBoss EAP 7.0.0に含まれるjackson-databindのバージョンが2.5.4だからです……が、複数のモジュール同士が依存関係にあり、単にjackson-databindを除外してもうまく動かないので、サブシステムJAX-RSを除外します。
参考:Wildfly 9 - How do I exclude Jackson
jboss-deployment-structure.xml
を以下のように設定します。
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">
<deployment>
<exclude-subsystems>
<subsystem name="jaxrs" />
</exclude-subsystems>
</deployment>
</jboss-deployment-structure>
では、この状態でJBossを再起動してみます。
やったぜ☆