木曜日 8 31, 2006
木曜日 8 31, 2006
JBoss Seamはバージョン1.0.1からGlassFishをサポートするようになりました。SJS AppServer 9 (glassfish)でSeamを動作させる方法の例は、Brian Leonard's Blogの以下のエントリを参照して頂くのがよいと思います。
上記の手順を参照すると、JBoss用に作られたSeamアプリケーションをGlassFish用に変更するポイントは以下の2点であることが分かります。
前者のライブラリの差分を埋める作業はたいした問題ではありませんが、後者のJNDI問題はSeam on GlassFishにとってちょっと厄介な問題です。もし、Seamで使用するセッション・ビーンが数十個あったら、GlassFishの場合はそれと同じ数だけ<ejb-local-ref>をweb.xmlに定義しなければならなくなってしまいます。
web.xmlにおける<ejb-local-ref>定義の煩わしさは、J2EE 1.4の時代から問題になっていましたが、Java EE 5からは@EJBアノーテーションによるEJB参照のインジェクションによって解決したことになっていました。JSF+EJB3の場合はバッキング・ビーンにEJB参照をインジェクションすることが期待されていたわけですが、Seamではそういうわけにはいきません。Seam+JSF+EJB3のアプリケーションではバッキング・ビーンを端折って、JSFページから直接セッション・ビーンのプロパティやビジネス・メソッドにバインディングできるようになっています([1])。この場合、EJBをアロケートする作業はWeb層のSeamランタイムが内部でJNDIルックアップを行っているわけですが、Seamランタイムは以下のようなデフォルトルールでejb-nameからのJNDI物理名を連想するようになっています。
<context-param>
<param-name>org.jboss.seam.core.init.jndiPattern</param-name>
<param-value>application-name/#{ejbName}/local</param-value>
</context-param>
JBossはベンダ独自仕様でEJBローカルに対してもJNDI物理名が付与されるようになっているため上記のcontext-paramの宣言さえ不要なのですが、GlassFishではEJBローカル参照にJNDI物理名は割り当てられないことになっています。GlassFishでは、EJBローカル参照の取得には必ずjava:comp/envで始まる環境コンテキスト名(論理名)を使用しなければならないため、web.xmlにおけるSeamのJNDI連想ルールを以下のように明示し、
<context-param>
<param-name>org.jboss.seam.core.init.jndiPattern</param-name>
<param-value>java:comp/env/ejb/#{ejbName}</param-value>
</context-param>
さらに、使用する全てのセッション・ビーンについて、以下のような<ejb-local-ref>宣言が必要になってしまいます。
<ejb-local-ref>
<ejb-ref-name>ejb/BookingListAction</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.BookingList</local>
<ejb-link>BookingListAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/RegisterAction</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.Register</local>
<ejb-link>RegisterAction</ejb-link>
</ejb-local-ref>
:
Java EEの仕様上、EJBローカル参照はEARアプリケーションを超えて参照することはできないため、グローバルな物理名を与える必要はなかったかもしれませんが、Java EE 5のEoDの観点からすれば、上記のような<ejb-local-ref>宣言列挙の状態は望むところではなかったでしょう。実際、GlassFishでもベンダ独自仕様としてEJBローカル参照にもデフォルトのJNDI物理名が与えられるようにしてほしいという要望は上がっているようです。
確かに、@EJBによるインジェクションができるオブジェクトは限られていますし、ステートフルなセッション・ビーンの場合はJNDIルックアップは避けられないわけですから、是非とも<ejb-local-ref>なしでEJBローカル参照が取得できるようになるように改善してほしいものです。
現状のSeam on GlassFish環境で、アプリケーション開発者ができるワークアラウンドとしては、web.xmlに<ejb-local-ref>を列挙する代わりに、@EJBが機能するWeb層のなんらかのオブジェクト(以前のエントリ「Java EE 5:Dependency Injection (2)」のインジェクション可能なクラスの一覧を参照)に、@EJBアノーテーションを列挙することが考えられます。これは、Web層における環境コンテキスト名のスコープは同一WAR内であり、WAR内のどこかで宣言した@EJB宣言の情報は同一WAR内の任意のクラスで参照可能であるというJava EE 5仕様を利用しています。例えば、ServletContextListenerを使用した場合の例として、以下のようなクラスを作成し、WARファイルに含めれば<ejb-local-ref>の宣言は不要になります。
@EJBs({
@EJB(name="ejb/BookingListAction", beanInterface=BookingList.class),
@EJB(name="ejb/RegisterAction", beanInterface=Register.class),
:
})
public class JndiResourceRefs implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) {}
public void contextInitialized(ServletContextEvent sce) {}
}
<ejb-local-ref>の宣言と@EJBの宣言は同じ意味のメタ情報ですが、Javaのソースで宣言した方が記述量が少なく、NetBeansなどのJDK 1.5対応のIDEを使用すればbeanInterface要素のインタフェース・クラスでコンプリーションが効くため、web.xmlをメンテナンスするよりも開発効率はいいと思います。
念のため、GrassFish v1 b48(Milestone 7)で動作確認もしてみましたが、web.xmlで<listener>宣言をしなくても、(リスナとしては機能しませんが)@EJBアノーテーションの効果はあるようです。
<!-- リスナ登録はなくても、@EJB宣言の効果はあった。
<listener>
<listener-class>com.example.JndiResourceRefs</listener-class>
</listener>
-->
幸いなことに、環境エントリ名を使用して修正したSeamアプリケーションは、JBoss上でもGrassFish上でも同じように動作することが期待されます([2])。JNDI物理名の代わりに環境コンテキスト名を使用することで、手間と引き換えにベンダ・コンパチビリティを得ることができる一つの例ですね。
[1] JavaOne SF 2006のSeamのセッションで、Gavin Kingが「バッキング・ビーンはノイズだ」と言っていたことが印象的でしたが、せっかくPOJOになったセッション・ビーンのビジネス・メソッドにJSFアクション・メソッドの縛り(String <actionName>()の形式であること)を持ち込むのは全ての場合において適切ではないと思います。例えば、入出力のあるWebサービスとステートレス・セッション・ビーンのハイブリッド形式は、Seamとの整合性がよくありません。将来的には、JAX-WSの@WebParamで実現されているパラメータ・インジェクション/バイジェクションのような仕組みを用いて、普通のビジネス・メソッドでもJSFからメソッド・バインディングできるようになることが期待されます。
[2] ただし、WebコンテナがServlet 2.4ベースのJBoss 4では環境エントリ名を使用した方式が利用できません。Servlet 2.5を含むJava EE 5フルスペックのJBoss 5になれば、@EJB方式/ejb-local-ref方式ともに利用可能になるでしょう。
火曜日 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ファイルが生成できるようにしています。
木曜日 8 24, 2006
Java EE 5は従来のJ2EE 1.4に較べて飛躍的にエンタープライズ・アプリケーションの開発を簡単にしました。しかし、Java EE 5仕様のAPIレベルでは開発効率を高めてくれてはいるものの、AppServerにデプロイするためのWARファイルやEARファイルの構成はJ2EE 1.4と同様であるため、そのビルド手順は相変わらず複雑です。
NetBeans 5.5はJava EE 5に対応しており、複雑にネストしたEARファイル構成であってもあまり苦労せずにデプロイ可能なアプリケーションをパッケージングすることができます。しかしながら、実際の開発プロジェクトでは、目的の成果物をビルドする以外にも、ドキュメント作成や品質管理、情報共有に関わる様々なタスクが存在します。これらのタスクは日々繰返し行なわなければならないため、ほとんどの開発プロジェクトではこれらのタスクを自動化する努力をしています。
現在のNetBeansやEclipseなどのIDEは、設計とビルド、および単体テスト辺りまでが守備範囲で、開発プロジェクトに関わるその他のタスクのサポートとオートメーションについては十分とは言えません。Apache Mavenは、開発プロジェクトのライフサイクルを自動化することを目的としたプロジェクト管理ツールの1つです。
Mavenはビルド・ツールのApache Antと比較されることが多いと思います。Antを使っても開発プロジェクトのオートメーション化は可能ですが、Mavenを使用すると自前のスクリプトをほとんど書かずに開発プロセスをオートメーション化することができます。
以下では、簡単なJava EE 5アプリケーションをMavenを用いて作成する手順をご紹介します。ここで使用するのはMaven 2.0ですので、まだMaven 1.0しか使ったことのない方にも参考になると思います。
Maven 2そのもののインストールは簡単です。Mavenのダウンロード・ページから、最新のバージョン2.0.4のアーカイブをダウンロードし、適当なディレクトリに展開し、そのディレクトリmaven-2.0.4/binを環境変数のPATHに設定してあげるだけです。以下は、シェルにbashを使用している場合の~/.bashrcの設定例です。
# Mavan export PATH=~/java/maven-2.0.4/bin:$PATH
以下のようにmvnコマンドが実行できれば基本的なインストールは完了です。
$ mvn --version
Maven version: 2.0.4
$ mvn --help
usage: mvn [options] [<goal(s)>] [<phase(s)>]
:
Mavenは必要に応じて、インターネット上のレポジトリ・サーバからプラグインやサード・パーティのライブラリをダウンロードします。もし、あなたのマシンがファイヤウォールの中にあるなら、~/.m2/settings.xmlというファイルを作成し、以下のようにHTTPプロキシーの設定をしてください。
<settings>
<proxies>
<proxy>
<active>true</active>
<protocol>http</protocol>
<host>proxy.mycompany.com</host>
<port>8080</port>
<username>your-username</username> <!-- if necessary -->
<password>your-password</password> <!-- if necessary -->
</proxy>
</proxies>
</settings>
Maven2のコマンドmvnでは、時々とても長いコマンドラインを入力しなければなりません。もし、あなたがシェルにbashを使用しているなら、以前のエントリ"Bash completion for Maven 2"に従って、bash completionの設定を行なって下さい。
もし、あなたがIDEとしてNetBeansを使用しているなら、Mavenide2-NetBeansプラグイン・モジュールをインストールするとよいでしょう。これにより、NetBeansからMaven2のプロジェクトをそのまま開くことができるようになり、簡単な操作ならNetBeans上でも行なうことができるようになります。インストール手順は、こちらにある通りに行なえばOKです。
Maven2プラグインのJava EE 5への対応状況を調べてみると、現状では十分な状況とは言えません。そのため、ここで使用するサンプル・アプリケーションの作成に必要なプロジェクト・テンプレート(Maven2用語でarchetypeと呼びます)を作成しました。以下のリンクにある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
上記により、archetypeモジュールがプラグイン・メタデータと共に、ローカル・リポジトリ(~/.m2/repository/)に保存されます。
【追記 2007/01/18】 Java EE 5 APIライブラリがhttps://maven-repository.dev.java.net/に登録されたため、以下の手順(ローカルリポジトリへのjavaee.jarのインストール)は不要になりました。
Java EE 5を構成する一部のAPIライブラリは、Maven2のセントラル・リポジトリにありますが、EJB3やJTA 1.1など重要なものが欠落しています。そのため、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
上記コマンドにより、以下のモジュールがローカル・リポジトリに保存されたことになります。
| groupId | artifactId | type(packaging) | version |
|---|---|---|---|
| sample.plugin | ee5-archetype-war-jsf | maven-plugin | 1.1 |
| javax.ee | javaee | jar | 5.0 |
先程インストールしたee5-archetype-war-jsfを雛型にして、Webモジュール用プロジェクトを作成します。プロジェクトの作成には、mvnコマンドにゴール名archetype:createを指定し、必要なパラメータを加えて実行します。ここでは、プロジェクト名をmywebappとしてプロジェクトを作成します。
$ mvn archetype:create -DarchetypeArtifactId=ee5-archetype-war-jsf \
-DarchetypeGroupId=sample.plugin \
-DarchetypeVersion=1.1 \
-DartifactId=mywebapp \
-DgroupId=com.example
上記の場合、コマンドラインのパラメータは全て指定しなければなりません。archetypeArtifactId/archetypeGroupId/archetypeVersionは先ほどインストールしたひな形モジュールのartifactId/groupId/versionです。artifactId/groupIdは作成しようとしているプロジェクトの成果物(WARモジュール)を一意に識別するための名前です。groupIdは、ソースツリーのベース・パッケージ名にも使用されます。artifactIdは成果物につける名前ですが、作成するプロジェクトの最上位のディレクトリ名としても使用されます。
上記のコマンド実行により、以下のようなソースコード・ツリーが作成されます。
mywebapp/
|-- 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
Maven2プロジェクトには必ずpom.xmlが含まれます。これはPOM(Project Object Model)と呼ばれ、プロジェクトのメタ情報をXML形式で表現したものです。mvnコマンドはこのPOMを読みとって、プロジェクト毎にカスタマイズされたタスクを実行するわけです。
このサンプル・プロジェクトはJSFページ1つ、バッキング・ビーン1つのだけの簡単なWebアプリケーションですが、すぐにWARを作成して動作を確認できるようになっています。
WARモジュールを作成するには、mvnコマンドにフェーズ名packageを指定して実行します。これにより、targetディレクトリにデプロイ可能なmywebapp.warが作成されます。
$ cd mywebapp/
$ mvn package
[INFO] Scanning for projects...
:
$ ls target
classes/ mywebapp/ mywebapp.war
mvnコマンドに指定したフェーズ名packageはそのプロジェクトの最終成果物(artifact)を生成するためのおまじないです。実際のところ、プロジェクトがejbモジュール用であろうと、スタンドアロン・アプリケーション用であろうと、あるいはプラグイン開発用のものであろうと、一貫して成果物を得るためのコマンドは mvn package でよいのです。
それでは動作を確認してみましょう。SJS AppServer 9 (glassfish)のasadminコマンドを用いてmywebapp.warをデプロイし、http://localhost:8080/mywebapp/にアクセスして、サンプルのページが表示されることを確認してみてください。
$ $ASROOT/bin/asadmin deploy target/mywebapp.war
ここで使用したサンプルのarchetypeは、Java EE 5のServlet 2.5/JSF 1.2仕様に基づいたものです。そのため、Servlet 2.4仕様までしかサポートしていないTomcatにはデプロイできないことに注意してください。
今回の例は、WARモジュール1つだけのシンプルな場合でしたが、Maven2の実力はマルチプロジェクトを連携したより複雑な場合に真価を発揮します。次回は、EJBモジュールを含めたマルチプロジェクトの場合を例に取り上げて紹介します。
月曜日 8 21, 2006
プロジェクト管理ツールMavenがバージョン2.0になってからは、バージョン1.0に較べてお世辞にも使いやすくなったとは言えません。例えば、プロジェクト・テンプレートを作成するのにMaven 1.0では、以下のように簡単なコマンドラインで済みました。
$ maven genapp
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects ~
|_| |_\__,_|\_/\___|_||_| v. 1.0
Enter a project template to use: [default]
Please specify an id for your application: [app]
Please specify a name for your application: [Example Application]
Please specify the package for your application: [example.app]
:
Maven 1.0ではパラメータで指定されなかったメタ情報については、インタラクティブに問い合わせてくれるため、実行するコマンドラインをシンプルにすることができました。
Maven 2.0では1.0のときのようなインタラクティブ・モードがないため、以下のように必須のパラメータについては全てコマンドラインに指定してあげなければなりません。
$ mvn archetype:create -DartifactId=app -DgroupId=example.app
[INFO] Scanning for projects...
:
Mavenのサイトの"Mini Guide: Guide to Maven 2.x auto completion using BASH"では、bash completionのためのスクリプトを紹介していますが、これはサンプル・スクリプトというレベルのもので一部のプラグイン名しか補間してくれませんし、パラメータについては全く補間してくれません。そこで、実用的なレベルで使用可能なスクリプトを作ってみましたので、ご紹介します。
設定方法は以下の通りです:
[ -f /etc/bash_completion ] && . /etc/bash_completion初期化用ファイルbash_completionのパスは、1.で導入したパッケージによって異なりますので注意して下さい。bashを再起動して、javaコマンドでのクラス名やcvsコマンドで引数の補間機能が働いていることを確認して下さい。
$ unzip m2.zip $ sudo cp m2 /etc/bash_completion.d/このコピー先のディレクトリも1.で導入したパッケージによって異なりますので注意して下さい。
デフォルトでは、Mavenのサイトにリストされている全てのプラグインのゴールと一般的によく使用されるフェーズ名の候補を列挙します。実際の利用シーンでは、プラグインのゴールを直接指定することはあまりなく、compile、test、packageなどのフェーズ名を指定することが多いかと思いますが、現状は全てのゴールとフェーズがリストされます。
$ mvn <TAB>
ant:ant package
antlr:generate plexus:app
archetype:create plexus:bundle-application
assembly:assembly plexus:bundle-runtime
assembly:directory plexus:descriptor
assembly:unpack plexus:runtime
castor:generate plexus:service
checkstyle:checkstyle plugin:addPluginArtifactMetadata
clean plugin:descriptor
clover:check plugin:report
clover:instrument plugin:updateRegistry
clover:log plugin:xdoc
clover:report project-info-reports:cim
compile project-info-reports:dependencies
compiler:compile project-info-reports:issue-tracking
compiler:testCompile project-info-reports:license
deploy project-info-reports:mailing-list
deploy:deploy project-info-reports:project-team
eclipse:add-maven-repo project-info-reports:scm
eclipse:clean projecthelp:active-profiles
eclipse:eclipse projecthelp:describe
ejb:ejb projecthelp:effective-pom
help:active-profiles projecthelp:effective-settings
help:describe rar:rar
help:effective-pom release:perform
help:effective-settings release:prepare
idea:idea resources:resources
install resources:testResources
install:install site
install:install-fire source:jar
jar:jar surefire:test
jar:test-jar taglist:taglist
javacc:javacc test
javacc:jjtree verifier:verify
javadoc:jar war:exploded
javadoc:javadoc war:inplace
jdepend war:war
また、ハイフン(-)までタイプして、タブを入力すると指定可能なオプションを列挙します。
$ mvn -<TAB>
--activate-profiles --reactor -e
--batch-mode --settings -f
--check-plugin-update --strict-checksums -fae
--debug --update-plugins -ff
--define --update-snapshots -fn
--fail-at-end --version -h
--fail-fast -B -npr
--fail-never -C -npu
--file -D -o
--help -N -r
--lax-checksums -P -s
--no-plugin-registry -U -up
--no-plugin-updates -X -v
--non-recursive -c
--offline -cpu
引数にプラグインのゴールが指定されている場合は、そのゴールで指定可能なパラメータを列挙します。
$ mvn archetype:create <TAB>-D<TAB> -DarchetypeArtifactId= -DarchetypeArtifactId=maven-archetype-archetype -DarchetypeArtifactId=maven-archetype-j2ee-simple -DarchetypeArtifactId=maven-archetype-mojo -DarchetypeArtifactId=maven-archetype-portlet -DarchetypeArtifactId=maven-archetype-profiles -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeArtifactId=maven-archetype-simple -DarchetypeArtifactId=maven-archetype-site -DarchetypeArtifactId=maven-archetype-site-simple -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeGroupId= -DarchetypeVersion= -DartifactId= -DgroupId= -DlocalRepository= -DpackageName= -DpomRemoteRepositories= -Dproject= -DremoteRepositories= -Dversion=
パラメータの補間時には、help:describeの出力結果を~/.bash/ディレクトリにキャッシュして補間候補に使用するようにしています。初めてタイプしたゴールのパラメータ補間時には内部でmvn help:describeを実行するため少し時間が掛かりますが、2回目以降はすぐに反応してくれます。
Maven 2.0.4の環境でほぼ問題なく動作することを確認していますが、何か問題などありましたらお知らせ下さい。改善提案なども大歓迎です。