#navi_header|Groovy| [[1103]] でGroovyServletを使った非常にミニマルなWeb開発環境を紹介しました。今回はもう少し掘り下げて、実際にServletやJavaソースを組み合わせ、Tomcat上でclassファイルの自動リロード(Context auto reload)も有効にした「サクサク開発」が出来る構成を作ってみたので、紹介します。 サンプル: - https://github.com/msakamoto-sf/java-groovy-gradle-war-sample1/tree/5359ba6592892a73201b28d2f7e18eaa583b0937 -- このサンプル自体は今後も調整を続ける可能性があるので、本記事で紹介している時点でのソースツリーは厳密には上記のものになります。 ''ポイント:gradle-tomcat-plugin を使う + Java/Groovyクラスファイルの出力先を"/WEB-INF/classes/"以下に設定する。'' gradle-tomcat-plugin: - bmuschko/gradle-tomcat-plugin · GitHub --- https://github.com/bmuschko/gradle-tomcat-plugin - vladvoic/tomcatGradleExample · GitHub -- https://github.com/vladvoic/tomcatGradleExample - Gradle Community Forums - Using gradle-tomcat-plugin -- http://forums.gradle.org/gradle/topics/using_gradle_tomcat_plugin 検証環境: Win7 64bit JDK7 64bit Gradle 1.4 * サンプルの構成 以下のように、実際に開発の現場で使いそうなポイントを組み込んでいます。 - "gradle tomcatRun" したあと、別のターミナルから"gradle classes"とかすれば、Tomcatがcontextをリロードしてくれるので「サクサク開発」が出来る。 -- 今回検証したのはコマンドラインとエディタを使った連携まで。EclipseやIntelliJ IDEAでも確実に「サクサク開発」を実践できるかまでは未検証。 - JavaソースとGroovyソースを組み合わせている。(JavaソースからGroovyソースのクラスを参照している) -- [[1163]] のテクニックを使ってます。 - Java/GroovyコンパイラにJavaソースのバージョンとか文字コード指定、デバッグON/OFFフラグを設定している。 ということで、ソースの詳細についてはGitHubで眺めていただき、ポイントとなる部分だけを紹介していきます。 * ポイント1 : Tomcatによるクラスファイル自動リロード ->「サクサク開発」 gradle-tomcat-pluginでは、"tomcatRun"タスクでTomcatを立ち上げる際に、Contextの"relodable"をデフォルトで有効化します。 これにより、"/WEB-INF/classes"および"/WEB-INF/lib"以下のファイルに変更があれば、Tomcat側で自動的にContextをリロードしてくれます。 ターミナルを3つ立ち上げ、1つは"gralde tomcatRun"でTomcatを起動させておき、2つ目ではエディタを立ちあげてソースファイルを編集し、3つ目で"gradle classes"を実行する、のような形で、コンパイルされたクラスを即座にTomcatに反映させる「サクサク開発」が実践出来ます。 ただし注意点が1つあります。GradleのJava/Groovyクラスファイル出力先はデフォルトでは以下のディレクトリです。 build/classes/... また、warプラグインが想定するデフォルトの"/WEB-INF/"ディレクトリは以下になります。 src/main/webapp/WEB-INF/... gradle-tomcat-pluginは、クラスファイルの出力先を設定から取得してContextに追加し、起動するため、基本的に上記のようなデフォルトのままでも起動自体は問題ありません。 ''しかし、TomcatがContextのリロードをしてくれるのはあくまでも "/WEB-INF/classes" や "/WEB-INF/lib" の中が更新された場合であり、 "build/classes" 以下が更新されてもスルーしてしまい、Contextはリロードされません。'' 解決策として、以下のようにJava/Groovyクラスファイルの出力先を"src/main/webapp/WEB-INF/classes"に設定してしまいます。 build.gradle: ... sourceSets.main.output.classesDir = 'src/main/webapp/WEB-INF/classes' ... これにより、"gradle classes"すると "src/main/webapp/WEB-INF/classes" 以下にクラスファイルを出力してくれますので、Tomcat側で変更を検出し、Contextをreloadしてくれます。 また、このままですと"gradle clean"した時に "src/main/webapp/WEB-INF/classes" を削除してくれませんので、"clean"タスクを以下のようにカスタマイズします。 clean { // add customized class output path to deletion targets of 'clean' task. delete << 'src/main/webapp/WEB-INF/classes' } これにより、"gradle clean"すると "build/" 以下に加え、"/WEB-INF/classes" も削除してくれます(※ただし、上記cleanタスクのカスタマイズは、「ん~、こんな感じでイケんじゃね?」と試してみたらうまく行ってくれただけですので、もしかしたら厳密にはカスタマイズ方法勘違いしてるかもしれません。) なお、あまりいじり過ぎたり何度もreloadが発生すると、Contextのreloadで以下のようなメッセージが表示されます。「怪しそうだな」と感じたら一旦Tomcatを停止して立ち上げ直すと良いのは、他のEclipseやIntelliJなどでの「サクサク開発」と同じです。 #pre||> $ gradle tomcatRun :compileJava UP-TO-DATE :compileGroovy :processResources :classes :tomcatRun Started Tomcat Server The Server is running at http://localhost:8090/warsample1 (...) The web application [/warsample1] created a ThreadLocal with key of type \ [org.codehaus.groovy.reflection.ClassInfo.ThreadLocalMapHandler] \ (value [org.codehaus.groovy.reflection.ClassInfo$ThreadLocalMapHandler@64658ba0]) \ and a value of type [java.lang.ref.SoftReference] \ (value [java.lang.ref.SoftReference@46b1e8de]) \ but failed to remove it when the web application was stopped. \ Threads are going to be renewed over time to try and avoid a probable memory leak. (...) ||< また、これで "gradle war" したwarファイルの中を覗いてみると、以下のようにクラスファイルのエントリが重複してしまっています。 #pre||> $ jar tf build/libs/warsample1-1.0.0.war ... WEB-INF/classes/net/glamenvseptzen/quickstart/ WEB-INF/classes/net/glamenvseptzen/quickstart/AdjustedGroovyServlet.class WEB-INF/classes/net/glamenvseptzen/quickstart/HelloServlet.class WEB-INF/classes/net/glamenvseptzen/quickstart/MyGroovyUtil.class WEB-INF/classes/net/glamenvseptzen/quickstart/MyStringUtil.class WEB-INF/classes/net/glamenvseptzen/quickstart/ToolKit.class WEB-INF/classes/net/glamenvseptzen/quickstart/AdjustedGroovyServlet.class WEB-INF/classes/net/glamenvseptzen/quickstart/HelloServlet.class WEB-INF/classes/net/glamenvseptzen/quickstart/MyGroovyUtil.class WEB-INF/classes/net/glamenvseptzen/quickstart/MyStringUtil.class WEB-INF/classes/net/glamenvseptzen/quickstart/ToolKit.class ... ||< ただし、展開すればちゃんと一つのみになっていますので、ひとまず心配はいりません。実際に単独でセットアップしたTomcatにdeployし、正常に動作することを動作確認しております。 * ポイント2 : JavaソースからGroovyのソースを参照 [[1163]] のテクニックを使ってます。以下のように、mainとtestの両方の "SourceSet" でJavaのソース指定を空っぽにして、Groovy側のコンパイラでコンパイルするように調整をしています。 build.gradle: #pre||> ... [sourceSets.main, sourceSets.test].each { // compile java and groovy file at same time. it.groovy.srcDirs += it.java.srcDirs // disable java compilation it.java.srcDirs = [] } ... ||< * ポイント3 : Javaソースのバージョン, 文字コード, デバッグ情報埋め込みの指定 GradleのJavaコンパイルのタスクも、Groovyコンパイルのタスクも、どちらも"Compile"タスクの型から派生しています。ということで、以下のようにすることでJava/Groovyの両方で共通してJavaソースのバージョン, 文字コード, デバッグ情報の埋め込みを設定できます。 #pre||> tasks.withType(Compile) { sourceCompatibility = '1.7' targetCompatibility = '1.7' options.encoding = 'UTF-8' options.debug = true } ||< * その他参考情報など EclipseとGraldeの連携という観点で色々と挑戦されているようです。 - Gradleを使ったWebアプリケーションのさくさく開発(セットアップ編) - splash of waters -- http://d.hatena.ne.jp/jappy/20130307/1362668792 今回、"tomcatRun"まではgradle-tomcat-pluginのおかげでスンナリ動いてくれましたが、Contextのリロードについて "/WEB-INF/classes" 「しか」見ないということに気づかず、数時間以上「おかしい・・・なんで "build/classes/..." 以下のファイルはちゃんと更新されてるのに、Tomcat側で反映してくれないんだ・・・」と悩んでました。 gradle-tomcat-pluginの過去のIssueなどを漁っているうちに、"tomcatGradleExample"を見つけて「あれ~??こっちはやっぱりちゃんとリロードしてくれる・・・どこが違うんだ・・・?」と見比べているうちに「あ、こっちは"/WEB-INF/classes"に出力するようにカスタマイズしてる。もしかして・・・」とTomcatのドキュメントとかソースまで手繰り寄せ、「やっぱり、"/WEB-INF/classes|lib"以下しか決め打ちで見てないんだ・・・」と確認でき、それでようやく、「じゃぁカスタマイズするか~。あ、でも、"tomcatGradleExample"って"gradle clean"しても"/WEB-INF/classes" 以下に残っちゃうよな・・・cleanタスクもカスタマイズが必要だ。」となり、それでようやくなんとか安定して動くようになった次第です。 あと、一応サンプルにはGradle添付のJettyプラグインも使えるようにしてます。ただ、こちらではContextの自動リロードをしてくれなかったのでスルーしてます。なんかGradle添付のJettyプラグイン、設定出来るパラメータも少ないし、gradle-tomcat-pluginと比べると大分見劣りがしてしまう・・・。 #navi_footer|Groovy|