火曜日 8 29, 2006
火曜日 8 29, 2006
Javaの開発プロジェクトでは、通常、何らかの方法で最終成果物のパッケージング手順を自動化しています。Java EEアプリケーションにおけるEARアーカイブを作成する場合、一般的に以下のステップを実施する必要があります。
上記の手順は開発するアプリケーションが異なっても実施すべき作業はほぼ同じです。プロジェクト管理ツールMavenでは、標準的な開発プロジェクトでよく使われる個々の手順をプラグインという形式でモジュール化し再利用することで、最終成果物のビルド手順を構築するのに、ほとんど自前のスクリプトを書かなくても実現できるようになっています。
以下では、Java EE 5仕様に基づく一般的なEARファイルをMaven2を使用して作成する手順を紹介します。
前のエントリの「Maven 2のインストール」に従って、Maven 2.0.4をインストールして下さい。
前のエントリと同様に、Maven 2のセントラル・レポジトリに不足しているJava EE 5ベースのプロジェクト・テンプレート(archetype)と、Java EE 5 APIライブラリをインストールします。以下のリンクにある3つのarchetypeモジュールをダウンロードし、ローカル・リポジトリにインストールします。
# war archetypeのインストール
$ mvn install:install-file -Dfile=ee5-archetype-war-jsf-1.1.jar \
-DgroupId=sample.plugin \
-DartifactId=ee5-archetype-war-jsf \
-Dversion=1.1 \
-Dpackaging=maven-plugin
# ejb archetypeのインストール
$ mvn install:install-file -Dfile=ee5-archetype-ejb-1.1.jar \
-DgroupId=sample.plugin \
-DartifactId=ee5-archetype-ejb \
-Dversion=1.1 \
-Dpackaging=maven-plugin
# ear archetypeのインストール
$ mvn install:install-file -Dfile=ee5-archetype-ear-1.1.jar \
-DgroupId=sample.plugin \
-DartifactId=ee5-archetype-ear \
-Dversion=1.1 \
-Dpackaging=maven-plugin
同様に、SJS AppServer 9 (glassfish)に含まれるjavaee.jarもローカル・リポジトリにインストールします。
$ cd $ASROOT/lib
$ mvn install:install-file -Dfile=javaee.jar \
-DgroupId=javax.ee \
-DartifactId=javaee \
-Dversion=5.0 \
-Dpackaging=jar
【追記 2007/01/18】 Java EE 5 APIライブラリがhttps://maven-repository.dev.java.net/に登録されたため、ローカルリポジトリへのjavaee.jarのインストールは不要になりました。
上記の一連のコマンド実行により、以下のモジュールがローカル・リポジトリに保存されたことになります。
| groupId | artifactId | type(packaging) | version |
|---|---|---|---|
| sample.plugin | ee5-archetype-war-jsf | maven-plugin | 1.1 |
| sample.plugin | ee5-archetype-ejb | maven-plugin | 1.1 |
| sample.plugin | ee5-archetype-ear | maven-plugin | 1.1 |
| javax.ee | javaee | jar | 5.0 |
Mavenプロジェクトの基本は、1プロジェクト→1成果物(artifact)です。ここで作成するearファイルは、1つのwarファイルと1つのejb-jarファイルを必要とするため、それぞれの成果物(earファイル、warファイル、ejb-jarファイル)毎にプロジェクトを作成することになります。また、ビルド・プロセス全体を面倒見るプロジェクトがあった方がいいため、作成するプロジェクトは全部で4つになります。以下の図に、サンプルのEARアプリケーション作成に必要なプロジェクトとそれぞれの関係を示します。
上図で分かるように、それぞれのプロジェクトは関連を持っています。上図におけるdependsというステレオタイプで示される関連は、「成果物Aを得るには、成果物Bが必要である」ということを表しています。もう一つは、aggregatesというステレオタイプで示される関連ですが、これは、Mavenの1プロジェクト→1成果物(artifact)という原則を拡張し、あるプロジェクトが複数の別プロジェクト(モジュール)と関係していることを表現することで、間接的に複数の成果物を一度に生成できるようにすることを表しています。これら2つの関連は、Maven2のプロジェクトメタファイルpom.xmlで表現されますが、具体的な表記方法については後で見ていくことにします。
次に、マルチモジュール・プロジェクトのディレクトリ・レイアウトを考えます。バージョン管理システムにソースコード・ツリーを登録してしまった後で、ディレクトリ・レイアウトを変更するといろいろと弊害がありますので、ディレクトリ・レイアウトについては将来のアプリケーションの拡張を考えて慎重に決めたいものです。一般的なearアプリケーションの場合は2通りぐらいの選択肢があります。1つは、ear用、war用、ejb-jar用のプロジェクトを全て同じ階層のディレクトリに置いてしまうものです。
myapp
|-- pom.xml # <packaging>pom</packaging>
|-- myapp-ear/
| `-- pom.xml # <packaging>ear</packaging>
|-- myapp-ejb/
| `-- pom.xml # <packaging>ejb</packaging>
`-- myapp-war/
`-- pom.xml # <packaging>war</packaging>
もう一つは、earモジュールとwar, ejb-jarモジュールの直接的な依存関係をディレクトリ構造に反映したものです。
myapp
|-- pom.xml # <packaging>pom</packaging>
`-- myapp-ear/
|-- pom.xml # <packaging>ear</packaging>
|-- myapp-ejb/
| `-- pom.xml # <packaging>ejb</packaging>
`-- myapp-war/
`-- pom.xml # <packaging>war</packaging>
どちらの構成を採用しても基本的には問題ありません。プロジェクト間の実際の関係はPOMファイル(pom.xml)で表現され、ディレクトリ構成で表現されるわけではないためです。また、後者の方針を採用した場合、モジュールの依存関係は必ずしもone-to-manyの関係だけではなく、many-to-oneの関係もあり得るため、全ての場合においてディレクトリの親子関係で依存関係を想像させることはできないことに注意して下さい。ここでは、前者のディレクトリ・レイアウトを採用して、プロジェクトを作成することにします。
上記で決定したディレクトリ・レイアウトに従って、マルチモジュール・プロジェクトを作成します。個々のプロジェクトの作成には、mvn archetype:createコマンドを用います。個々のプロジェクトは異なる種類の成果物(artifact)を生成します。プロジェクトがどのタイプの成果物を生成するかの情報は、POMファイルの<packaging>要素で表すようになっています。
トップレベルのプロジェクトmyappは単に複数プロジェクトのaggregationの役割を担うわけですが、この場合<packaging>要素にはpomを使用します。ここでは、packagingがpomのプロジェクトを作成するのに、Maven2が標準で持っているarchetypeの1つmaven-archetype-site-simpleを指定してトップレベルのプロジェクトmyappを作成します。
$ mvn archetype:create -DarchetypeArtifactId=maven-archetype-site-simple \
-DartifactId=myapp \
-DgroupId=com.example
これによって、以下のようなディレクトリ・ツリーが作成され、
myapp/
|-- pom.xml
`-- src/
`-- site/
|-- apt/
| `-- index.apt
`-- site.xml
myapp/pom.xmlの内容は以下のようになっていると思います。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
</project>
次に、myappディレクトリの中に、3つのプロジェクトmyapp-war、myapp-ejb、myapp-earをmvn archtetype:createコマンドを使用して作成します。
$ mvn archetype:create -DarchetypeArtifactId=ee5-archetype-war-jsf \ -DarchetypeGroupId=sample.plugin \ -DarchetypeVersion=1.1 \ -DartifactId=myapp-war \ -DgroupId=com.example $ mvn archetype:create -DarchetypeArtifactId=ee5-archetype-ejb \ -DarchetypeGroupId=sample.plugin \ -DarchetypeVersion=1.1 \ -DartifactId=myapp-ejb \ -DgroupId=com.example $ mvn archetype:create -DarchetypeArtifactId=ee5-archetype-ear \ -DarchetypeGroupId=sample.plugin \ -DarchetypeVersion=1.1 \ -DartifactId=myapp-ear \ -DgroupId=com.example
上記のコマンドの実行により、プロジェクトmyappの基本的なディレクトリ・レイアウトが出来上がりました。
myapp
|-- myapp-ear/
| `-- pom.xml
|-- myapp-ejb/
| |-- pom.xml
| `-- src/
| |-- main/
| | |-- java/
| | | `-- com/
| | | `-- example/
| | | |-- Sample.java
| | | `-- SampleImpl.java
| | `-- resources/
| | `-- META-INF/
| | `-- ejb-jar.xml
| `-- test/
| `-- java/
| `-- com/
| `-- example/
| `-- SampleImplTest.java
|-- myapp-war/
| |-- pom.xml
| `-- src/
| `-- main/
| |-- java/
| | `-- com/
| | `-- example/
| | `-- Page.java
| |-- resources/
| `-- webapp/
| |-- META-INF/
| | `-- context.xml
| |-- WEB-INF/
| | |-- faces-config.xml
| | `-- web.xml
| |-- index.html
| `-- page.jsp
|-- pom.xml
`-- src/
`-- site/
|-- apt/
| `-- index.apt
`-- site.xml
ここで、Maven2の非常に興味深い振る舞いに言及しておかなければなりません。最初に作成したルート・プロジェクトmyappのpom.xmlの内容をもう一度確認してみてください。以下のように、<modules>要素がいつの間にか追加されていることに気がつくと思います。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<modules>
<module>myapp-war</module>
<module>myapp-ejb</module>
<module>myapp-ear</module>
</modules>
</project>
POMファイルにおける<module>要素は、Maven2プロジェクトのaggregationの関連を表すものです。実はmvn archetype:createが実行される度に、この要素が追加されていたのです。Maven2では、プロジェクトのディレクトリ内でサブ・プロジェクトを作成した場合、デフォルトでaggregationの関連が設定されることが分かります。
生成されたmyapp-ejbプロジェクトには、以下のようなサンプルのセッション・ビーンSampleImplクラスが含まれています。
package com.example;
import javax.ejb.Stateless;
@Stateless
public class SampleImpl implements Sample {
public String hello(String name) {
return "Hello, " + name + "!";
}
}
ここでは、Web層のモジュールからこのSampleImplビーンを呼び出すように修正した後でEARファイルを作成してみることにします。
Web層のmyapp-warモジュールは、JSFベースのサンプルになっています。page.jspのフォームがsubmitされた時に、上記のSampleImplビーンが呼び出されるように修正してみましょう。Java EE 5環境ではマネージド・ビーンに対して、EJB参照のインジェクションが利用できるため、page.jspのバッキング・ビーンPage.javaの修正は、以下の赤字で示した部分だけです。
package com.example; import javax.ejb.EJB; import com.example.Sample; public class Page { @EJB Sample sample; // EJB参照のインジェクション private String name; : public String hello() { message = sample.sayHello(name); // EJBのメソッド呼出し return "success"; } }
myapp-warのPageクラスのコンパイルにはcom.example.Sampleインタフェースを含むmyapp-ejb.jarが必要になります。この場合、POMファイルに<dependency>要素を用いて明示的なモジュール間の依存関係を示してあげます。デフォルトで作成されたmyapp-war/pom.xmlには、javaeeモジュールとjunitモジュールへのdependencyが予め定義されていますので、これに加えてmyapp-ejbモジュールへのdependency定義を以下のように追加します([1])。
<project>
:
<groupId>com.example</groupId>
<artifactId>myapp-war</artifactId>
<packaging>war</packaging>
:
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-ejb</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${basedir}/../myapp-ejb/target/myapp-ejb.jar</systemPath>
</dependency>
:
</dependencies>
</project>
これにより、myapp-warモジュール内のPageクラスのコンパイル時のクラスパスにmyapp-ejb.jarが付け加わることになります。
それでは、myappパッケージをビルドして、このプロジェクトの最終成果物であるEARファイルを作成してみましょう。そのためには、ルート・プロジェクトであるmyappディレクトリに移動した後、mvnコマンドにフェーズpackageを与えて実行するだけです。
$ cd ~/src/myapp
$ ls
myapp-ear/ myapp-ejb/ myapp-war/ pom.xml src/
$ mvn package
[INFO] Scanning for projects...
:
$ ls myapp-ear/target/
application.xml myapp/ myapp.ear
ディレクトリmyapp/myapp-ear/target/内に、EARファイルmyapp.earが作成されていることが確認できると思います。
myappプロジェクトに伝えた指令packageは、関連モジュールであるmyapp-ear、myapp-war、myapp-ejbにも伝えられます。Mavenランタイムは、各プロジェクトのpackage指令に対して、何を実行すればよいかをPOMの情報から決定し、適切な処理を適切な順番で実行し、最終成果物であるmyapp.earを生成してプロセスを終了しました。
AppServerにデプロイしてアプリケーションの動作を確認するには、以下のようにasadminコマンドを実行し、http://localhost:8080/myapp-war/にアクセスします。
$ $ASROOT/bin/asadmin deploy myapp-ear/target/myapp.ear
上記のEARファイル作成のマルチモジュール・プロジェクトの作成手順を一通り実行してみると、初めてMaven2に触れた方は何か手品のようなものを見せられた気分になるのではないでしょうか。この手品の種は、適切にデザインされたMaven2のビルド・ライフサイクルに対して、適切なプラグインが用意され、よくデザインされたデフォルト・ルールに従ったプロジェクトを作成すれば、ビルド・プロセスの構築についてあまり多くの設定を必要としないということを示しています。少なくとも、Apache Antを使ってビルド・スクリプトを自前で作成する場合に較べて、Maven2を用いたアプリケーション開発は格段の生産性向上をもたらす可能性があると言えます。
このサンプルを通して私が感じることは、Ruby on Rails (RoR)との類似性です。一般に、Java EE開発者がRoRに魅力を感じつつも、未知の言語に対する学習曲線の問題やパフォーマンス、安定性に不安を感じているため、実際のアプリケーション開発にRoRを採用しようと判断するのはかなり難しいのではないかと思われます。
また、Javaの世界でもRoRに触発されたGrailsやtrailsといったRails風の開発ツールが提供されようとしています。これらのツールは、Rubyの代わりにJavaプラットフォームが利用できるという安心感はあるものの、別の不安要素が残されています。例えば、Grailsは、ビジネスロジックやWebページ・テンプレートにGroovyスクリプトを使うことが必須であるため、パフォーマンスに対する不安があります。一方のtrailsは、EJB3+JPAがベースになっていることは嬉しい限りですが、Web層のフレームワークが日本ではあまり知られていないApache Tapestryを使わなくてはならず、開発者の学習に対するオーバヘッドが問題になりそうです。
しかしながら、Maven2を使った開発では、開発プロセスに対してのみデフォルトの規約を与えているだけでありながら、これだけ簡単にJava EEアプリケーションを作成することができるのです。しかも、アプリケーションを構成する要素技術やフレームワークの選択に制約はなく、アプリケーション設計者が自由に選ぶことができるのです。
RoRやGroovyが持つシステム変更に対する即効性は、現在のJava EE標準仕様だけでは太刀打ちできないのかもしれませんが、JVMのスクリプティング言語に対するパフォーマンスの改善やPhobosがいずれこれらの問題を解決してくれるのではないかと期待せずにはいられません。
また、Maven2によるアプリケーション開発はシステムの構成を必然的にマルチモジュール構成として捉え、モジュール間の依存関係を意識したシステム設計を余儀なくされます。これは、開発システムが巨大であっても、破綻することのないアーキテクチャを維持することに役立つ特徴であると言えます。
もちろん、Maven2にも不安要素はあります。1つは、簡単なことは簡単にできるのですが、ちょっと変わったことをしようと思うと、すぐにはどうやって解決したらいいか簡単には分からず、問題解決のためにプラグインのソースコードやインターネットを彷徨い歩く羽目になり易い点です。この問題は他のRails開発ツールに共通する問題のように思います。Maven2の救いは、それがツールの問題であり、開発しているシステムのアーキテクチャに影響を与えるものではないということです。Maven2はAntのスクリプトを呼び出すことができるため、簡単に解決できなければ、問題の部分をAntスクリプトで書いてしまえばいいわけです。
これまで、Maven 1.0はAntと同じぐらい多くの開発プロジェクトで使用されてきています。Maven 2.0はMaven 1.0の数々の問題点解決し、より拡張性に優れたアーキテクチャになっています。あくまでも個人的な予想ですが、Maven 2.0はMaven 1.0と同程度の普及率を今後も維持していけるだろうと予想しています。
そろそろMaven2のレールに乗ってもいい頃かもしれません。
[1] この部分のdependencyの修正は、本来は以下のように<scope>要素をcompileで定義する方が一般的かも知れません。
<dependency>
<groupId>com.example</groupId>
<artifactId>myapp-ejb</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
しかし、この場合、Maven2のwarプラグインは、依存しているmyapp-ejb.jarをローカル・リポジトリから探そうとしてしまうため、myapp-ejb.jarをmvn installする必要があります。個人的には、無闇にartifactをローカル・リポジトリにインストールするのはあまり好きではないので、中間成果物であるmyapp-ejb.jarのパスをsystemPathで与えてあげることにより、プロジェクト・ライフサイクルをinstallフェーズまで進めなくても、earファイルが生成できるようにしています。