Tech Sketch Bucket of Technical Chips by TIS Inc.

JBossAS7のクラスローダーを理解しよう

Pocket

techsketch-banner-OSS+startingblock(700x65).jpg

JBossのAPサーバー JBossAS7 は、RedHat社による商用サポート版の JBossEAP6 (バージョン番号は異なっていますがAS7に基づいています)もリリースされ、使用できる機運が高まってます。
以前、 新しくなったJBossASを使ってみよう で運用管理面を、 SAStrutsをJBossAS7で動かそう でSAStrutsの移植を中心にJBossAS7の技術紹介をしましたが、今回はクラスローダーに着目します。
JBossAS7のクラスローダーは、JBossAS5の階層型と呼ばれるものから、モジュール型と呼ばれるものに変更されたことで、共有ライブラリの設定方法が大きく変わりました。共有ライブラリとしてSeasar2のユーティリティクラス S2Util のライブラリをJBossAS7にインストールすることを通して、JBossAS7のクラスローダーを紹介します。


そもそもクラスローダーとは

Java言語でのクラスローダーとはどのようなものでしょうか?クラスローダーはJavaクラスをJava仮想マシン(JVM)に動的にロードしますが、このクラスローダーは複数存在し、階層構造(親子関係)を持ちます。クラスローダーがクラスをロードする場合、図1のように最初に親のクラスローダーにその処理を委譲し、見つからなければ自身で処理を行います。
階層型クラスローダー.png

次に、参考までにAPサーバーTomcat7のクラスローダーを見ていきます。 Tomcat7のクラスローダー は図2のように"parent-child tree"という階層型の構造をとりますが、サーブレット仕様の規約 Servlet Specification, version 2.4 (section 9.7.2 Web Application Classloader)により、親のCommonよりも子のWebアプリケーションを先に検索します。
Tomcatクラスローダー.png

Tomcat7で共有ライブラリを用いる場合は、 Common のクラスローダーがすべてのWebアプリケーションから参照可能なので、ここに配置できます。このディレクトリは、定義ファイル"conf/catalina.properties"内の"common.loader"

で定義されています(環境変数のcatalina.base,catalina.homeは明示的に指定しなければTomcat7のインストールディレクトリです)。
よって、S2Utilを共有ライブラリとして使用するのは、s2util-0.0.1.jarをTomcat7インストールディレクトリ直下のlibに配置することで可能です。

従来のJBossは

では、JBossAS5を対象に、共有ライブラリがどのように扱えるかを見ていきます。JBossAS5では、定義ファイル"server/default/conf/jboss-service.xml"内の"classpath"

で、JBossAS5がどのライブラリをロードするかが定義されており(環境変数のjboss.server.lib.urlはJBossAS5インストールディレクトリ直下の"server/default/lib"、jboss.common.lib.urlはディレクトリ"common/lib"を指します)、すべてのWebアプリケーションはこれらのライブラリを参照可能です。
よって、S2Utilを共有ライブラリとして使用するのは、s2util-0.0.1.jarをディレクトリ"server/default/lib"かディレクトリ"common/lib"に配置、あるいは定義ファイル"jboss-service.xml"にこのJARファイルの在処のディレクトリを登録することで可能です。
このJBossAS5のクラスローダーは、サーブレット仕様の規約 "Servlet Specification, version 2.4"とは異なり検索順はデフォルトでは子の"WEB-INF/lib"からではなく先に親の"server/default/lib"等に委譲しますが、定義ファイル"jboss-web.xml"内の"java2ParentDelegation"

false と設定すれば、先に子の"WEB-INF/lib"から検索を行います。

階層型からモジュール型への移行

この階層型クラスローダーの欠点として、APサーバーの実装(設定)次第でクラスローダーを親から検索するか、子から検索するかで紛らわしいところがあり、APサーバーが持つライブラリと、Webアプリケーションが必要とするライブラリのバージョンが異なった場合に、どういう設定をすればよいか悩むところがあります。このため、大規模なアプリケーション開発、保守になるほど、 JAR の地獄 (Jar Hell)と呼ばれる世界に陥り、保守性が低下します。
これを解消するため、Java言語の標準として、階層型のクラスローダーではなくモジュール型の採用の検討が古くから行われています(例: JSR294 Improved Modularity Support in the JavaTM Programming Language など)。この動きは現在は、 Project Jigsaw で検討されていますが、Java 9に導入が延ばされる模様です( Project Jigsaw: Late for the train より)。
このような流れの中で、JBossAS7はいち早くこのモジュール型の考えを導入し、将来的にJigsawがリリースされれば互換性を保つと表明しています。

JBossAS7でのモジュール

それでは、モジュールとはどのようなものでしょうか?モジュールはクラス、JAR、リソースの集合で、他のどのモジュールに依存するかを定義し、自身のクラスローダーを持ちます。
JBossAS7でのモジュールの定義の例を見ていきます。JBossAS7ではサンプルでH2のデータソースが定義され、H2のJDBCドライバがモジュールとして登録されています。モジュールに関連するファイルは次のようになります。

ディレクトリ構造の"com/h2database/h2"はモジュールの名称に基づいており、モジュール名称は"com.h2database.h2"です。定義ファイル"module.xml"は下記のように、このモジュールのリソースとしてH2のJDBCドライバのh2-1.3.161.jarを"<resources>"に、依存するモジュールの"javax.api"等を"<dependencies>"に登録します。

このモジュールの使用は、JBossAS7の起動コマンド

から分かるように、Javaのクラスパス"-classpath"の指定はなく、"-mp"でモジュールが配置されたディレクトリを指定することで、アプリケーションが必要とするモジュールはmodulesのディレクトリ配下で検索されます。

S2Utilのモジュール登録およびその使用

S2Utilのライブラリを新規のモジュールとして登録する方法を示します。
まず、モジュール名称の決定、ディレクトリの作成が必要です。上記のH2の例ではJARファイル内のパッケージ名とモジュール名は異なっていますが、通常は分かりやすいようにS2Utilのパッケージ名と同じ"org.seasar.util"をモジュール名として用います。よって、modules配下にディレクトリ"org/seasar/util/main"を作成し、S2Utilのライブラリs2util-0.0.1.jarを配置し、下記の定義ファイル"module.xml"を作成します。

なお、S2Utilの場合は、結果論として依存しているモジュールの"<dependencies>"の指定は不要です。もし必要な場合に登録が漏れていると、このモジュールを使用するWebアプリケーションのデプロイ時に次のような java.lang.LinkageError が発生しますので、 ClassNotFoundException になったクラス(本例ではjavax.servlet.Filter)が存在するモジュール(本例ではjavax.api)を"<dependencies>"に登録していきます。

一方、このモジュールを使用するWebアプリケーションは定義ファイル"WEB-INF/jboss-deployment-structure.xml"内の"<dependencies>"

で、参照する(依存する)モジュールを指定します。これにより、WebアプリケーションでS2Utilのライブラリが参照可能となります。

使用に際してのポイント

・モジュールに登録するJARファイルに外部のプロパティファイルが必要な場合は、そのファイルを"modules/モジュール名/main/prop"のようなディレクトリに纏めて配置し、定義ファイル"module.xml"のリソース定義にそのディレクトリ名を指定します。

・JBossAS5で稼働させていたWebアプリケーションを移植する場合、上述の定義ファイル"jboss-web.xml"で"java2ParentDelegation"の定義があると、JBossAS7ではそのような概念は存在しないので、デプロイエラーとなります。その場合は、定義ファイル"jboss-web.xml"から"class-loading"の設定をすべて削除する必要があります。また、必要なモジュールを定義ファイル"jboss-deployment-structure.xml"に登録します。
・JBossAS7ではログ出力系のlog4j,slf4jが、モジュール"org.apache.log4j", "org.slf4j"として標準に用意されており、これに関してはWebアプリケーションでは定義ファイル"jboss-deployment-structure.xml"に登録しなくても利用可能です。

最後に

APサーバーを長く運用していますと、例えばORマッパーでHibernateを使用している場合、WebアプリケーションAではVer.3を用い、WebアプリケーションBではVer.4を用いるという状況が起こりえます。Tomcat7,JBossAS5ではそれぞれのクラスローダーの特性を把握しながら、ライブラリの配置を行わなければならず、本当に正しいバージョンのライブラリを用いているかの検証が取りづらく、アプリケーションを動かして、初めて何かがおかしいと気がつくことがままあり得ます。
これに対し、JBossAS7でモジュールとして登録する場合は、ライブラリに明示的にバージョン番号を付与し、Webアプリケーションからはどのバージョンを使用するかの指定を行います。これにより同じライブラリに複数バージョンが存在していてもどれを使用するかを明確に指定でき、クラスローダーの問題で生じる障害の防止が容易に行えます。
JBossAS7のモジュール型のクラスローダーは始めは取っ付きにくいかもしれませんが、従来の混沌とした階層型クラスローダーを一新するもので、いずれJigsawがJavaSEで実装されると他のAPサーバーも追随するはずの概念ですので、安心してお使いください。

エンジニア採用中!私たちと一緒に働いてみませんか?