BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Modular Java:それは何なのか?

Modular Java:それは何なのか?

ブックマーク

原文(投稿日:2009/09/23)へのリンク

ここ数年にわたって、Javaのモジュール化は活発に議論され続けている話題である。(今は機能していない)JSR 277からJSR 291や進行中のJSR 294にいたるまで、モジュール化はJavaの進化における必要なステップであるとみられている。ScalaのようなJVMベースの将来の言語でさえモジュール化について検討している。Modular Javaに関する一連の記事の最初となるこの記事では、モジュール化とは何を意味し、なぜそんなことを気にしなければならないのか、について議論する。

モジュール化とは何か?

モジュール化は個々のモジュールを開発することができるようにする方法をソフトウェアの開発に対して適用する一般概念である。各モジュールは多くの場合、モジュールがやりとりをするための標準化されたインターフェースを持つ。実際のところ、オブジェクト指向言語におけるオブジェクト間の関心の分離のようなものは、その規模がより大きいことを除いてまったく同じ概念である。概して、システムをモジュールに分解することによって、結合度を最小化することができ、それによりコードの維持管理をより容易にすることができる。

Javaは(導入時にModula-3モジュールになぞらえたというpackage以外には)モジュールのことを念頭において設計されてはいなかったにもかかわらず、Javaコミュニティでは多くの事実上のモジュールが存在している。Log4JからHibernate、Tomcatにいたるまで、どんなJavaライブラリも事実上モジュールである。一般的に言って、オープンソースのアプリケーションもクローズドソースのアプリケーションもいくつかの外部ライブラリへの依存性を持っているはずであり、それによって結果的にさらに別のライブラリへの依存性を推移的に持つことになる。

ライブラリもモジュールである

ライブラリは暗黙的なモジュールである。ライブラリは必ずしもやりとりするための単一のインターフェースを持っているというわけではないだろうが、多くの場合(使われるためにあるべき)'パブリックな'APIとユースケースを文書化する'プライベートな'パッケージを持っている。さらに、それらのライブラリは(JMXJMSといったライブラリへの)依存性をそれら自身持っている。この結果、自動依存性マネージャは、正確に必要としているよりもずっと多くのものを持ち込むことになる。Log4J-1.2.15の場合には、10以上の(javax.mailjavax.jmsを含む)ライブラリ依存関係を持ち込むが、これらの多くはLog4Jを使用するプログラムではまったく必要とされないものである。

いくつかの場合には、モジュールの依存関係は任意である。すなわち、モジュールは依存モジュールを欠落させることで機能の一部のみを提供する。前述の例では、JMSが実行時クラスパス上に存在しなければ、JMSを使ったロギングは利用可能ではないが、他のメカニズムが利用可能になるだろう。(Javaはこのことを遅延リンクによって実現している。遅延リンクとはクラスがアクセスされるときまでに存在していればよく、依存ライブラリが見つからない場合には適切なClassNotFoundExceptionによって扱う仕組みである。他のプラットフォームには実行時チェックと全く同じものを提供する弱いリンクを持つものもある。)

一般に、モジュールにはバージョン番号が割り当てられる。多くのオープンソースプロジェクトはlog4j-1.2.15.jarのように名付けられたリリースをつくる。これによって開発者は、実行時の手動検査によってではあるが、オープンソースライブラリの特定のあるバージョンが使われているかどうかをクラスパスを調査することによって決定することができる。しかし、プログラムは異なるバージョンのライブラリに対してコンパイルされていることが多い:暗黙の仮定はlog4j-1.2.3.jarに対してコンパイルしてlog4j-1.2.15.jarに対して動かしても挙動としては互換性がある、ということだ。次のマイナーバージョンにアップグレードするだけなら一般には互換性がある(これが log4j 1.3 での問題が結果として互換性のない新しいブランチ 2.0を作り出すことになった理由である)。これらの多くは一般的に制約よりもむしろ実行時に知ることができる規約をもとにしているのである。

どんなときにモジュラリティが有効なのか?

モジュラリティはアプリケーションを異なる部分に分解するための一般的概念として有用である。それによって、別々にテスト(そして進化)することができる。上述の通り、多くのライブラリはモジュールであるため、ライブラリを作り出す人にとってもライブラリを使う人にとっても、モジュラリティは理解すべき重要な概念である。たいてい、依存関係情報はビルドツール(maven pomやivy-moduleなど)にエンコードされており、ライブラリの利用方法の記述の中に明示的に文書化されている。上位のライブラリが下位のライブラリのバグの回避方法を組み込むことも珍しくなく、最新バージョンの下位ライブラリが修正されたとしても、上位レベルライブラリでの違和感のない使用感を提供するために組み込まれることもある。(時にこのことが微妙な問題を引き起こしうるけれど。)

もしライブラリが他の人が利用するためにビルドされているならば、それだけで暗黙的にはモジュールである。しかし、“Hello World”ライブラリが多く存在しないのと同じように、実際の“Hello World”モジュールも多くは存在しない。アプリケーションが十分に大きくなったとき(あるいは、十分にモジュール化されたビルドシステムで構築されたとき)にはじめて、アプリケーションを論理的に異なる部品に分割するという概念が効果を発揮する。

モジュール分割の恩恵の一面はテストの側面である。(適切に定義されたAPIを持つ)小さなモジュールは一般に一枚岩のアプリケーションよりもテストがしやすい。このことは特にGUIアプリケーションであてはまり、GUI自身は簡単にテストすることができるわけではないかもしれないが、GUIが呼び出しているコードは比較的簡単にテストすることができる。

もう一つの側面は進化の側面である。システムは全体としてバージョン番号を持つけれども、現実的には表には現れない複数のモジュールやバージョンが組み合わせられた製品である。(クローズドソースであろうがオープンソースであろうがそのシステムの依存モジュールであるある種のライブラリが – Javaバージョンでさえ – 必ず存在する)。結果的に、各モジュールはそのモジュールにあった方法で自由に進化することができる。他のモジュールに比べて早く進化するモジュールもあるし、その一方で長い期間変化する必要のないくらい安定なものもある。(例えば、Eclipse 3.5にはorg.eclipse.core.bootがあるが、これは2008年2月から変更されていない。)

プロジェクト管理もモジュール化から恩恵を受ける。一つのモジュールが公開APIを持って他の人がそれに対するコードを書くことができるようになれば、別のチームが別のモジュールを実装することが可能である。このことは、大きな規模のプロジェクトではどうしても避けられないことであり、サブチームに別のモジュールの納期に対して責任を持たせることができる。

つまり、アプリケーションをモジュール化することは依存するライブラリのどのバージョンを利用しているかを明確に識別することを可能にし、大きなプロジェクト全体のライブラリの依存性の調和に役立つ。

実行時vsコンパイル時

Javaは一般に、コンパイル時か実行時かにかかわらずフラットなクラスパスを持っている。すなわち、アプリケーションは普通クラスパス上にみつかるどんなクラスにたいしても、(少なくともオーバーラップのない前提では、つまり最初に見つかったものを採用する前提では)クラスパスでのエントリの順序にかかわらず完全な可視性を持っている。このことによってJavaでは動的リンクの機能が実現されている。つまり、クラスパスの前方からロードされたクラスは、クラスパスの後方のクラスの全ての参照を実際に必要とされるまで解決する必要がない。

このことは、実装が実行時まで不明であるようなインターフェースの集合を機能させるために頻繁に利用されている。例えば、SQLユーティリティは一般的なJDBCパッケージに対してコンパイルされているが、実行時には(設定情報をいくつか加えて)正しいJDBCドライバをインスタンス化する。これは一般的には(事前に定義されたファクトリインターフェースか抽象クラスを実装する)クラス名を実行時にClass.forNameルックアップで与えることで実現している。指定されたクラスが存在しなければ(または別の何らかの理由でロードできなければ) エラーが生成される。

その結果、コンパイル時のクラスパスがモジュールの実行時のクラスパスと(微妙に)異なることは起こりそうなものである。さらには、しばしば各モジュールが(モジュールAはモジュールC 1.1に対してコンパイルされ、モジュールBはモジュールC 1.2に対してコンパイルされるというように)単独でコンパイルされるが、実行時には一つのパスの中に結合される(今回の例では、モジュールCのバージョン1.1か1.2のうちの任意に選ばれたどちらかだけを参照すること)ことも起こる。これは即座に依存性地獄につながり、特にそれが実行時クラスパスを形成するこれらの依存性の推移閉包である場合に起こる。MavenIvyのようなビルドシステムはエンドユーザではなく開発者に対してモジュール化を可視化してくれる。

Javaはクラスローダと呼ばれるそれほど賞賛されてはいない機能を持っていて、それによって、実行時のパスをさらに細分化することができる。一般には、全てのクラスはシステムクラスローダによってロードされる。しかしながら、実行時の空間を異なる複数のクラスローダに分けているシステムもある。良い例はTomcat(や他のサーブレットエンジン)であり、多くの場合一つのウェブアプリケーションに一つのクラスローダを持っている。これによって、ウェブアプリが普通に機能しながら、同じJVM助運の他のウェブアプリに寄って定義されたクラスを(偶然だろうとそれ以外の理由であろうと)見ることができないようになっている。

これが機能するための方法は各ウェブアプリが独自のクラスローダでクラスをロードすることであり、(局所的な)ウェブアプリの実装は他のウェブアプリの実装と衝突するようなクラスをロードしないことである。このことが要求することは、どんなクラスローダチェーンに対してもクラススペースが一貫しているということだ。これは、一度に2つのクラスローダからロードされた2つのUtil.classを保持することが、もしそのクラスローダが互いに不可視であるならば、可能であるという意味である。(それはまた、サーブレットエンジンが再起動することなく変更を際デプロイすることができるようにしているものでもある。クラスローダを捨てることで、そのクラスの参照も捨て去り、古いバージョンをガベージコレクションに食べさせることができる - これはサーブレットエンジンが新しいクラスローダを作成して、実行時に新しいバージョンのクラスを再ロードすることを可能にしている。)

隅から隅までモジュール

モジュラーシステムを構築することは、まさにアプリケーションを(潜在的に)再利用可能なかたちのモジュールに分割する方法であり、モジュール間の結合を最小化することである。そしてまた、モジュールの要求を分断する方法である。例えば、Eclipse IDEは一般的に(例えばjdt.uijdt.coreのように)GUIおよび非GUIコンポーネントにそれぞれ依存性を持つプラグインを備えている。このことによって、非GUIモジュールはIDE環境の外部で他の用途(ヘッドレスビルドや構文解析、エラーチェックなど)に利用することができる。

モノリシックなrt.jar以外にも、いかなるシステムも一般に様々なモジュールに分解可能である。ここで問題が思い浮かぶ。その価値があるといえるか、ということだ。結果として、モジュールシステムとして開始して作業を進めた方が、モノリシックなシステムを分解してモジュールに切り分けるよりもずっと簡単なのだ。

このことが一般的であるといえる理由の一つが、モジュール境界を越えたクラスの漏洩に対処することである。例えば、java.beansパッケージは論理的には、いかなるGUIコードに対する依存性をも持つべきではない。しかしながら、Beans.instantiate()で使われているjava.beans.AppletInitializerAppletへの参照を保持しており、それによって当然のごとくAWT全体への波及的な依存性を持つ。そのためjava.beansは技術的にはAWTに対しては選択的な依存性を持つが、常識的にはそうであるべきではないことがわかる。もし仮にコアJavaライブラリ構築当初、もっとモジュラーな方法がとられていれば、このようなエラーはAPIが公開されるずっと前に捕まえられていたであろう。

ある時点でモジュールはそれ以上サブモジュールに分解することができなくなる。しかしながら、時々関連する機能の組織化が容易であるという理由で同じモジュールの中に残され、必要となったときにだけさらに分解される、ということがある。例えば、リファクタリングのサポートはもともとEclipse JDTの一部であったが、(CDTのように)他の言語が一般的なリファクタリング機能を利用することができるために独自のモジュールとして引き出された。

プラグイン

多くのシステムはプラグインの概念を通して拡張可能である。このような場合には、ホストシステムはプラグインが適合しなければならない定義されたAPIと、プラグインを注入する方法を持っている。(ウェブブラウザやIDE、ビルドツールといった)多くのアプリケーションは正しいAPIを提供するプラグインを与えることによってアプリケーションをカスタマイズする方法を提供する。

時に、これらのプラグインは制限されていて(オーディオやビデオをデコードするような)一般的操作を行うことしかできないが、(たとえばIDEのプラグインのように)それだけで同程度に十分複雑になり得る。またある時には、これらのプラグインはその振る舞いをさらにカスタマイズするための独自のプラグインを提供することがあり、それによってシステムをさらにカスタマイズ可能にする。(しかしながら、間接的なレベルの数が増大するにつれてシステムがどんどん理解しにくいものになる。)

プラグインAPIは個々のプラグインが従わなければならない契約の一部を形成している。これらのプラグインはそれ自体モジュールであり、それを包含するシステムが提供する通常の依存連鎖やバージョニングの課題にぶつかる。(特定の)プラグインAPIの複雑さが増すにつれ、プラグインそのものも同じように複雑になる(か、後方互換性の振る舞いをメンテナンスしなければならなくなる)。

ブラウザでのNetscapeプラグインAPIの成功の一つの理由は、その簡潔さにあった。片手で数えられるほどの機能だけが必要とされ、ホストブラウザがインプットを適切なMIMEタイプで変換することで、プラグインはその残りの処理を扱うことができる。しかしながら、IDEのようにもっと複雑なアプリケーションは一般にずっと密接に統合されたモジュールが必要であり、それゆえそれらのモジュールを扱うためのもっと複雑なAPIを必要とする。

Javaモジュラリティの現状

多くのモジュールシステムやプラグインインフラがJavaの世界に現状存在している。IDEは一般によく知られたものであり、IntelliJ、NetBeans、Eclipseはすべてその使い方をカスタマイズする方法として独自のプラグインシステムを提供している。しかし、ビルドシステム(AntやMaven)、エンドユーザアプリケーション(Lotus Notes、Mac AppleScriptを利用できるアプリケーション)はそのアプリケーションやシステムの主要な機能を拡張することができる、というやり方を独自に持っている。

ほぼ間違いないと言っていいだろうが、Javaにおける最も成熟したモジュールシステムはOSGiであり、Javaそのものとほぼ同じくらい長い歴史がある。最初に現れたのはJSR 8としてであるが、ずっと最近になってJSR 291として承認された。OSGiはJARのMANIFEST.MFの中にメタデータを追加定義し、パッケージ単位での必要な依存関係を示すようにしている。これにより、モジュールが(実行時に)依存関係が適合しているかどうかをチェックすることが可能になり、加えて各モジュールが(モジュールごとにClassLoaderを持っているおかげで)独自のプライベートなクラスパスを持つことを可能にしている。このことによって、上述した依存性地獄の問題から救われるが、完全には防ぐことはできない。JDBC同様、OSGiは複数のオープンソース(と商用)実装をもつ仕様(現在はリリース4.2である)である。モジュールはどんなOSGiの特定のコードにも依存する必要がないので、多くのオープンソースライブラリは今やOSGiランタイムで使うためのマニフェストの中にライブラリのメタ情報を埋め込んでいる。そうでないものに対しては、bndのようなツールが既存のJARファイルを後処理して、実用にたえるデフォルト値を生成することができる。Eclipse 3.0がOSGiに移行したのは2004年であり、それ以前はプロプライエタリなプラグインシステムを利用していた。他のプロプライエタリなカーネルを持つシステム(JBoss、WebShepre、Weblogic)も後に続き、そのランタイムのベースをOSGiカーネルの上に構築している。

さらに最近になって、JigsawプロジェクトがJDKそのものをモジュール化するために開始された。JDK内部の部品であり、他のSE 7実装ではサポートされない可能性もあるが、JigsawをJDK外部で利用することは禁止されていない。Jigsawは前述したまだ作業中のJSR 294のリファレンス実装になる可能性もある。 SE 7の最小バージョンが要求することが意味することは(現状Java 7は存在しないという事実とあわせて)Jigsawはまだ作業進行中であり、Java 6やそれ以下で稼動するシステムでは一般には利用可能ではない、ということである。

標準的なモジュール化形式の採用を奨励すべく、JSR 294のExpert Groupは現在、simple module system提案について議論を続けている。(MavenリポジトリやApache.orgのようなところで見つかるような)Javaライブラリの作成者で、このグループにいる誰かがJigsawやOSGiシステムの両方から使用可能なメタ情報を提供するだろう。Java言語の少しの変更(もっとも注目すべきはmoduleキーワードの追加である)とあわせて、この情報は十分に先進的なコンパイラであればコンパイル時に生成することができるのではないだろうか。(JigsawやOSGiのような)ランタイムシステムはこの情報をインストールされたモジュールやその依存関係の検証にりようすることができるかもしれない。

まとめ

この記事ではモジュール化の一般的概念とそれがJavaシステムでどのように実現されているかを論じてきた。コンパイル時と実行時のパスは異なるかもしれないので、依存性地獄を引き起こすようなライブラリの一貫性のない要求がある可能性がある。しかしながら、プラグインAPIによって多くのタイプのコードがロードされるだろう。それらはホストの依存性解決の結果でなければならないし、一貫性の欠如に対する責任を増す。このことを防ぐために、OSGiのような実行時モジュールシステムは、アプリケーションが何も言わずに失敗したり、実行時に検出不可能な方法で失敗したりするのではなく正しく開始されるかどうかを決定する以前に要求を検証することができる。

最後に、JSR 294メーリングリストにおいてJava言語のJava言語仕様全体で定義されることになるモジュールシステムを作る作業は進行中である。それによって、Java開発者は、エンコードされた依存性情報をもつバージョンづけられたモジュールを生成することができるようになる。その依存性情報は結果的にどんなモジュールシステムでも使われることになるだろう。

この記事に星をつける

おすすめ度
スタイル

BT