WildFlyのクラスローディング

#はじめに

特に要望はありませんでしたが、[WildFlyをワイルドに動かしてみる](https://qiita.com/tama1/items/829be5aacd81637ae73a)の続きの記事を書きます。今回はクラスローディングについてです。今回は2018年12月時点で最新の `WildFly-15.0.0.Final` を使用します。

#WildFly のクラスローディング

WildFly に含まれる jar はモジュールという考え方で管理されています。モジュールには静的モジュールと動的モジュールの2種類があります。

##モジュールの種類

**1.静的モジュール**
　modules ディレクトリ配下にインストールされた jar を指します。ユーザーが作成したカスタムモジュールの jar を置くこともできます。
**2.動的モジュール**
　war や ear ファイル内にある jar やクラスを指します。

アプリケーションから静的モジュールを使用する場合、アプリケーション内にデプロイメント記述子ファイル`jboss-deployment-structure.xml`を置き、このモジュールを使いますよという宣言である**依存関係**を記述する必要があります。

ただし、静的モジュールの中には**暗黙的**な**依存関係**を持つモジュール[^2]があり、これらは`jboss-deployment-structure.xml`に記述しなくても勝手にロードされます。

##クラスローディングの優先順位
クラス(モジュール)のロードには優先順があり、同じパッケージの同名クラスがあった場合は先にロードされた優先順位が高いものが使用されます。

各モジュールは以下の順番[^1]でロードされます。

1. 暗黙的依存関係の静的モジュール[^2](System Dependencies)
2. ユーザーが `jboss-deployment-structure.xml` に設定した静的モジュール(User Dependencies)
3. 動的モジュール(Local Resource)


##本当かどうか確認してみる
疑り深いので本当かどうか試してみます。

###確認用JSPの作成
暗黙的依存関係のモジュールの中の`org.apache.log4j`を使って確認します。まず下記のような jsp を用意します。

```java:logger.jsp
<%@page contentType="text/html; charset=UTF-8" %>
<html>
<body>
<%
Object logger = org.apache.log4j.Logger.getLogger("test");
out.print(logger.getClass().getName());
out.print("<br>");
out.print(logger.toString());
%>
</body>
</html>
```

###確認用にせクラスの作成
以下のような偽の Logger クラスを作成、コンパイルして classloadTestInModules.jar に固めます。これを**静的モジュール**として modules ディレクトリ配下にインストールします。

```java:Logger.java
package org.apache.log4j;
public class Logger {

	public static String getLogger(String name) {
	//org.apache.log4j.Loggerクラスの替わりに文字列moduleを返す。
		return "module";
	}
}
```

次に少しだけ変更して、同様にコンパイルして classloadTestInWar.jar に固めます。これは**動的モジュール**としてアプリケーション(war)に含めます。

```java:Logger.java
package org.apache.log4j;
public class Logger {

	public static String getLogger(String name) {
	//org.apache.log4j.Loggerクラスの替わりに文字列warを返す。
		return "war";
	}
}
```

###静的モジュールのインストール
作成した classloadTestInModules.jar を modules ディレクトリ以下にインストールします。

以下のディレクトリを作成します。
　wildfly-15.0.0.Final/modules/system/layers/base/test
　wildfly-15.0.0.Final/modules/system/layers/base/test/test
　wildfly-15.0.0.Final/modules/system/layers/base/test/test/main

下記に classloadTestInModules.jar を置きます。
　wildfly-15.0.0.Final/modules/system/layers/base/test/test/main

`module.xml`を以下の内容で作成します。`test.test` がこのモジュールの名前になります。

```xml:module.xml
<?xml version="1.0" encoding="UTF-8"?>
<module name="test.test" xmlns="urn:jboss:module:1.7">
    <resources>
        <resource-root path="classloadTestInModules.jar"/>
    </resources>
</module>
```

次に `wildfly-15.0.0.Final/standalone/deployments` にある適当なアプリケーション `helloworld.war` の `WEB-INF/lib` に `classloadTestInWar.jar` war 直下に `logger.jsp` を置きます。デプロイメントマーカーファイル `helloworld.war.dodeploy` を作成してデプロイさせて準備完了です。

###暗黙的依存関係のモジュールが使用される？

ブラウザで`http://localhost:8080/helloworld/logger.jsp`にアクセスします。

以下のように表示されました。ちゃんと Logger クラスのオブジェクトが戻ってきていますので、暗黙的依存関係のモジュールのクラスが返ってきたことが分かります。

**org.apache.log4j.Logger**
**org.apache.log4j.Logger@62af7a58**

想定通りに動きました:clap:

###ユーザーが置いた静的モジュールが使用される？

暗黙的依存関係のモジュールではなくユーザーが指定したモジュールを使いたいという設定を入れてみます。`helloworld.war/META-INF` にデプロイメント記述子ファイル `jboss-deployment-structure.xml` を以下の内容で作成します。

```xml:jboss-deployment-structure.xml
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
		<exclusions>
			<module name="org.apache.log4j" />
		</exclusions>
		<dependencies>
			<module name="test.test"/>
		</dependencies>
    </deployment>
</jboss-deployment-structure>

```
`<dependencies>`は使いたい静的モジュールの名前です。`<exclusions>`は使いたくない暗黙的依存関係のモジュールの名前になります。ここで再度、`helloworld.war.dodeploy` ファイルを作成してデプロイしなおし `http://localhost:8080/helloworld/logger.jsp` にアクセスします。

以下のように表示されました。 module という内容の String 型が戻ってきていますので、インストールした test.test モジュールのクラスが使用されたことが分かります。

**java.lang.String**
**module**

想定通りに動きました:clap:

###動的モジュールが使用される？

次に動的モジュールのみ使用するように設定します。 `jboss-deployment-structure.xml` を以下の内容に変更します。暗黙的依存関係モジュールを除外する設定のみ残します。

```xml:jboss-deployment-structure.xml
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
		<exclusions>
			<module name="org.apache.log4j" />
		</exclusions>
    </deployment>
</jboss-deployment-structure>

```

再度、`helloworld.war.dodeploy` ファイルを作成してデプロイしなおし `http://localhost:8080/helloworld/logger.jsp` にアクセスします。

以下のように表示されました。 war という内容の String 型が戻ってきていますので、WEB-INF/lib にある動的モジュール classloadTestInWar.jar のクラスが使用されたことが分かります。

**java.lang.String**
**war**

想定通りに動きました:clap:

#まとめ

WildFlyがクラスをロードする優先順位を意識していないと、作成したアプリケーションの中に暗黙的依存関係のモジュールと同じ物が含まれていた場合、バージョンの違などによる思わぬエラーに悩まされることがありますのでご注意下さい。

#参考資料
[^1]:クラスローディングの優先順 ⇒ [1.3. Class Loading Precedence](http://docs.wildfly.org/15/Developer_Guide.html#class-loading-precedence)
[^2]:暗黙的依存関係のモジュール一覧 ⇒ [2.3. Which are the implicit module dependencies?](http://docs.wildfly.org/15/Developer_Guide.html#Implicit_module_dependencies_for_deployments)
[第6章 JBoss EAP クラスローディング](https://access.redhat.com/documentation/ja-jp/red_hat_jboss_enterprise_application_platform/7.0/html/configuration_guide/overview_of_class_loading_and_modules)