MavenプロジェクトでのServletプログラミングで、Eclipse上で「サクサク」、つまりJavaソース修正→Eclipse上で保存→Eclipseによる自動コンパイル→即座にServletContainer上に反映という素早い開発サイクルを実現するためのメモです。
対象:フルスペックのJ2EEサーバ機能を用いない、TomcatやJettyなどの軽量ServletContainer上で動作するWebアプリケーション。
環境:Maven3, Eclipse 3.6 or 3.7 以上, m2e プラグイン, Tomcat 5 以上
Java Servlet プログラミングで何がプログラマを苛立たせるか。それは "deploy" 作業であると思います。Java Servlet プログラミングで根強い人気を誇る Sysdeo Eclipse Tomcat Launcher Plugin は、Plugin側でTomcatを起動して、Plugin側でEclipseプロジェクトにあわせてContextを自動設定してくれます。これによりプログラマは "deploy" について一切気にする必要が無くなり、Eclipse上でJavaソースを修正→即座にTomcatがContextをリロード→ブラウザ上から動作確認することが出来ます。また、Seasar2 による SAStruts フレームワークではhot deploy機能により、classファイルの更新を検知して自動的に最新のclassファイルをロードしてくれます。これによりプログラマは修正する度に "deploy" する必要は無くなります。
ここまでであれば特に難しい問題はありません。Eclipseプロジェクトの出力ディレクトリを"WEB-INF/classes/"に設定し、Servlet Container側で適切にContextのドキュメントベースを設定し、最後にServlet Container側でclassファイルが更新されれば自動的にContextをリロードする設定して準備完了です。
問題が出てくるのはMavenプロジェクトの場合で、WEB-INFを含めたWebコンテンツは "src/main/webapp/" 以下に、ビルドされたclassファイルは "target/classes/" 以下に、と分断されてしまいます。そのままですとどうしても一度warまでパッケージングした後、ServletContainerにdeployさせる必要が出てきます。
この問題を解決するためには jetty-maven-plugin や tomcat-maven-plugin を導入します。これらを使うことで、"src/main/webapp/" と "target/classes/" の分断をplugin側で適切にハンドリングしてそれぞれのServlet Containerをデバッグ起動することが可能となります。
ここまでで、"deploy" を省略するためのアプローチを以下に整理します。
Jettyは軽量のJava Servlet Containerです。jetty-maven-pluginをMavenプロジェクトに組み込むことで、"mvn jetty:run" でMavenのディレクトリ構成に従ってServletContainerが起動します。これにより、Mavenでビルド or m2eプラグイン導入済みのEclipse側で自動コンパイルされたclassがJetty側でも間を置かずに自動リロードしてくれるようになります。
Mavenのplugin内部でTomcatをLaunch可能にします。当初は codehaus で開発されていましたが、バージョン 2.0 以降は Tomcat プロジェクト側に開発が移ったようです。
以下注意点です。
本記事では、以下の理由から バージョン 1.2 のtomcat-maven-plugin を使ってみます。
環境: MacOS X 10.7.3 $ mvn -version Apache Maven 3.0.3 (r1075438; 2011-03-01 02:31:09+0900) Maven home: /usr/share/maven Java version: 1.6.0_31, vendor: Apple Inc. Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home Default locale: ja_JP, platform encoding: SJIS OS name: "mac os x", version: "10.7.3", arch: "x86_64", family: "mac" Eclipse 3.7 Java EE M2E - Maven Integration for Eclipse 1.0
archetypeとしてcodehausのwebapp-jee5を使って "Hello, World" のServletサンプルコードを作成します。
$ mvn archetype:generate … Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : org.codehaus.mojo.archetypes:webapp-jee5 Choose archetype: 1: remote -> org.codehaus.mojo.archetypes:webapp-jee5 (-) Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1 Choose version: 1: 1.0 2: 1.0.1 3: 1.1 4: 1.2 5: 1.3 Choose a number: 5: Define value for property 'groupId': : test Define value for property 'artifactId': : servlet1 Define value for property 'version': 1.0-SNAPSHOT: : Define value for property 'package': test: : test.servlet1 Confirm properties configuration: groupId: test artifactId: servlet1 version: 1.0-SNAPSHOT package: test.servlet1 … $ cd servlet1/ $ find . . ./pom.xml ./src ./src/main ./src/main/java ./src/main/java/test ./src/main/java/test/servlet1 ./src/main/webapp ./src/main/webapp/index.jsp ./src/main/webapp/WEB-INF ./src/main/webapp/WEB-INF/web.xml
とりあえずEclipseにインポートし、以下のJavaクラスを追加します。
src/main/java/test/servlet1/Hello.java:
package test.servlet1; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Hello extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<body>"); out.println("Hello, World!"); out.println("</body>"); out.println("</html>"); out.close(); } }
src/main/webapp/WEB-INF/web.xml に以下のようにservlet設定を追加します。
<?xml version="1.0" encoding="UTF-8"?> <web-app …> <display-name>servlet1</display-name> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- append start --> <servlet> <servlet-name>Hello</servlet-name> <servlet-class>test.servlet1.Hello</servlet-class> </servlet> <servlet-mapping> <servlet-name>Hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <!-- append end --> </web-app>
"mvn package" で target/servlet1-1.0-SNAPSHOT.war が生成されれば、一旦適当なTomcatのwebappsディレクトリにコピーし、deployして動作確認しておきます。
まずcodehausのsnapshotリポジトリURLをpom.xmlに組み込みます。
2012-04現在、codehausではsonatypeのNexus ( http://www.sonatype.com/Products/Nexus-Professional ) を運用していますので、以下のURLからsnapshotリポジトリのURLを確認できます。
... <repositories> <repository> <snapshots> <enabled>true</enabled> </snapshots> <id>codehaus-snapshots-group</id> <name>Codehaus Snapshot Repositry</name> <url>https://nexus.codehaus.org/content/groups/snapshots-group/</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <snapshots> <enabled>true</enabled> </snapshots> <id>codehaus-snapshots-group</id> <name>Codehaus Snapshot Repositry</name> <url>https://nexus.codehaus.org/content/groups/snapshots-group/</url> </pluginRepository> </pluginRepositories> ...
続いてtomcat-maven-pluginを組み込みます。
... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <!-- Copy & Paste Start --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.2-SNAPSHOT</version> <configuration> <path>/</path> </configuration> </plugin> <!-- Copy & Paste End --> </plugins> </build> ...
では "mvn tomcat:run" を実行してみます。依存jarのDLが終わった後、Tomcatの起動メッセージが表示されます。
"Starting Coyote HTTP/1.1 on http-8080" のメッセージが表示されたら、ブラウザでlocalhostの8080番にアクセスしてみます。"<path>"要素にて、ContextPathを "/" にしていますので、
http://localhost:8080/hello
にアクセスすれば Hello クラスのServletが実行されます。
上手く動かない場合は、すでに8080番をListenしているサービスがいないか、pom.xmlの記述をtypoしていないかなど確認してみてください。
また起動されたTomcatは、Ctrl-Cで終了できます。
ここまで確認できれば、あとはEclipse上からMaven Buildの "tomcat:run" ゴールをデバッグ実行します。この時、デバッグビューにて実行中のMaven Buildを右クリックしてソースルックアップパスを適切に設定しておきましょう。
あとはブレークポイントを設定するなどして、自由にデバッグ実行が可能です。また、Eclipse上でJavaソースを編集して保存すると、即座にTomcat側にも反映されます。
tomcat-maven-pluginには他にも、warをTomcatにdeployしたり、deploy中のアプリ一欄を表示したりと色々便利なゴールが用意されています。また "tomcat:run" ゴール自体にも"fork"を始めとするオプションパラメータを設定できますので、CIサーバ上で結合テストを実施するなどにも活用できます。
本記事をきっかけとして、実際の現場でtomcat-maven-pluginを活用していただければ幸いです。