タイトル/名前 | 更新者 | 更新日 |
---|---|---|
日記/2013/04/28/力を抜こうと書いてあるだけなのになんで怠けるなと怒られるの。 | msakamoto-sf | 2013-04-28 22:42:00 |
Groovy/Maven/Examples (includes "Java Joint Compile") | msakamoto-sf | 2013-04-28 00:11:47 |
Groovy/Maven/GMaven + Eclipse memo | msakamoto-sf | 2013-04-22 01:08:06 |
Groovy/GroovyServlet でファイルアップロード (multipart-formdata) | msakamoto-sf | 2013-04-13 22:11:14 |
Groovy/Gradle/Equivalent to Maven's "provided" scope | msakamoto-sf | 2013-04-13 16:57:52 |
Groovy/Gradle/Get Fullpath For Dependencies Jar Files | msakamoto-sf | 2013-04-13 15:31:06 |
日記/2013/04/08/本日の調査メモ : Jetty8, winstone再考, ServletのSessionのThreadSafe性 | msakamoto-sf | 2013-04-08 01:27:58 |
Groovy/TemplateEngine/Hogan.groovy | msakamoto-sf | 2013-04-07 18:39:58 |
Groovy/真・超お手軽Web開発(Groovy-1.8 + Jetty8, 201304版) | msakamoto-sf | 2013-04-07 14:58:20 |
Groovy/Gradle/Eclipseとの連携 | msakamoto-sf | 2013-04-06 22:48:36 |
(ついにこれを書く時が来たか・・・。)
※本エントリはmsakamoto-sfの個人的な見解で、所属する会社の業務内容や本人の業務に対する姿勢とは関係ありません。(いや、内容としては若干関係ありますが、別に会社に対する不満とかそういう視点にはつながりません)
→
→
いや何と言うか、多分元の脱社畜ブログの趣旨としては、皆して他人の仕事を手伝って貢献し「なければならない」で縛りあって仕事してるのはおかしいから、度を越したら、職責範囲できっちり線を引こうよ、という内容なのかなーと思っていたら、なんか「それやったら何もできない人になる」とか、まるで怠け者になるのを推奨しててケシカラン!みたいな内容が出てきたりして、やっぱり議論というか人間として何か物を書いたり言ったりして表現したくなる、その背景というのは大事だよなぁと。
まぁ話の筋としては脱社畜ブログの返しで自分としては頷いて終わりですし、そろそろはてブのホットエントリから外れつつあるのであと数日で忘れ去られるトピックでしょう。
仏教的には喜捨がーとか布施のーとか中道がーで終わる話で。
なので、あまりにもドライなのは潤滑剤が足りなくなりそうなのでなんだかなーです。
とはいえ、個々人がお手伝い出来るリソースにもそれぞれなりの限界があるわけでして。
給料にカウントされないのであれば、あるいはされたとしても、やっぱり「ゴメン、私も手伝えるのはここまで!」というリミットが存在するわけです。
あるいは手伝いではなく普通に仕事を任された場合でも、「いや~、こんなに立て続けに仕事積まれても、これが限界でしょ!」というリミットが存在するわけです。
なので、職責がどうの~とかそこまでドライに考えなくても、あっぷあっぷになりそうなら「お手上げ」しちゃっても良いわけです。
だから、頑張り過ぎちゃう人向けにはもっとお手上げしちゃっても良くて、あるいはそれすら難しそうなら、止むを得ずドライに「じゃ、お先に」って帰っちゃっても良いわけです。
「仕事に対する責任が~」という話もありますが、別に誰も、怠けたくて「これは俺の仕事じゃない」なんて言い出すわけじゃないかと。(怠けたくてそんな事言い出すような人は、もうちょっと別の面で精進してもらう必要が。)
自分が提供できるリソースに、限界が来てしまったので、やむなく「ゴメン、これは無理だったわ・・・」となるわけです。別に怠けたくて仕事を放り投げるわけではないはずです。その人なりのベストであったりベターを尽くした結果「お手上げ」になったので、そこはまぁ、もうちょっと早めに状況を共有するとか、周りでサポートするとか月並みな意見になりますが、何も、怠けたくて悪気があってお手上げするわけじゃ無いのだから・・・と思わなくも無いです。(もちろん限度や普段の人付き合いや勤務態度はあると思いますが)
で多分脱社畜ブログが問題視してるのは、それが出来ない環境というか世間様というところで。
「~しなければならない」というのは「~しておけばこうなる」のを期待した縛りかな~と。で、それがまさしくgothedistanceでも書かれている「何にもできない人になる」という一種の脅迫でして。
スキルアップしないと生き残れませんよ、この先生きていけませんよ、餓え死にますよ、ほらあの貧乏人を見なさい、ああなりたくは無いでしょう?と。
だからもっとビジネスマンにならないと行けませんよ、自分で仕事を作れる人間にならないとイケませんよ、云々。
結局、恐怖を植えつけて、それに対する焦燥感とか漠然とした不安感でもって、延々と努力し続け、頑張り続けることを強いられてる・・・というのは語弊があって、知らず知らずのうちに勝手に自分の中でそうしたジャスティスを組み立てて自縄自縛してるのではないかなと。
それに対する返答はまぁ仏教的には「空」であったり「中道」であったり、あるいは上座部仏教を持ちだしてもマッチすると思います。
自分の中の根っことして「こうしたい」というピュアな衝動というか方向性(あえて「欲望」とは書きません)に基づいて他人と自分の幸福のために頑張るのは多分本人としてもやりがいを感じてラッキーなんでしょうが、そうでなくて、「ああはなりたくない」「みんなああしてるから、自分もこうしないと駄目なんじゃないか」と漠然とした不安や恐怖、焦燥感にじりじりとパワーを使い潰していくのは、あまりハッピーな状況じゃないと思います。別にそれでも悪い結果が出るとは限らないのですが、でもあんまりそればっかりに長くリソース配分していくと、月並みな表現ですが心が擦り切れていきますので、やっぱり心身ともに良い影響は無いと思います。
個人的には、極端な話ですが、人生一度は1年くらいニート生活してみると良いと思うんですよね。何もすることが無くて只の消費に溺れてしまうようであればそれは後々苦労しそうなので正したほうが良いでしょうし、自分で勝手に何かしら社会貢献・地域貢献・家族貢献的なことに動き出す人はそれで人生見つめなおしてもらえば良いと思いますし。
怠けては自分や他人に良い影響は与えられませんが、かといって力みすぎて、力み続けても、いつか自分や周囲に疲れが溜り、やれうつ病だー、突然会社に来なくなったー、だの、やっぱり自分や周囲に悪い影響が出てしまいます。
ほどほど(中道)、それが自分の中でどれ位なのか試してみて、適度に無理しない程度に、うまーく「力を抜く」やり方で毎日を過ごしたほうが、良い関係が長続きしそうに思うんですがどうなんでしょうかね?
なんというか、スポーツでは「力を抜く」というのが、ある程度のスキルレベルに到達した目安と捉えられてると思うんですが、なんでスポーツよりもっと人生に密着する「お仕事」で「力を抜く」というのがこんなにも前後の文脈ごっそり無視されて「怠けてたら飢え死にしますよ~、スキルアップできませんよ~、そんな人生嫌ですよね~?」と言われちゃうのか、ワタクシ, サパーリワカリマセン。
「~しなければならない」に縛られて毎日を爆進してると、人によっては生活習慣病にかかって寿命縮めますよ。
もっとヤバイと、生活習慣病とか病気になる前に、心身を病んでしまってドロップアウトしますよ。
ドロップアウトすると、マジで本当に「何もできない人」になっちゃいますよ?
そうはなりたくないですよね?
あなたの周囲にもいませんか?突然会社に来なくなってそのまま辞めちゃった人とか、いきなり休んで、うつ病やメンタル系の病気ですと診断書出してきて長期休養に入った人とか。
自分がそうなってしまったからこそ言っちゃいますが、そうはなりたくないですよね?
だから、「程々」にしましょうよ。食事するときも腹八分目、お仕事するときも腹八分目にしましょうよ。
仕事が溜まりに溜まって鬱々としだしたら、八分目に戻しましょうよ。
(いや、疲れを知らずノリノリでランナーズハイになってる人はそのままペース配分注意しつつゴールを目指してもらっても良いと思いますが。でも、少なくとも今自分が走ってるコースは短距離走なのか中距離走なのかフルマラソンなのかは把握して、ペース配分は考えたほうが良いと思いますが。)
そして、同じように「程々」を見つけて「お先に失礼」と変える同僚を、「あいつはあんなんだからスキルアップしないんだ」とか思わずに、普通に「お疲れ様~」と見送りましょうや。
そうすれば、今が地獄だとしても、多分地獄がもう一歩退いてくれるんじゃないかな~と考えてます。
で、どうしても限界が来てしまって、助けを求める余裕も無くお仕事で「ヤッチマッタ」とか極端な話ドロップアウトしてしまったとしたら・・・
その時は、「自分の体のほうが・心のほうが、『もう無理だから一旦安め』と壊れる前にブレーキをかけたのだ」と考えれば良いのです。(無宗教向け。仏教・キリスト教・イスラム教などであれば、「仏様が」とか「神様が」、一旦休めと言ってくれたのだ、と信者であれば自力で組み立ててくれると思います)
それによる周囲への悪影響や自分に対する評判に戦々恐々とするかもしれませんが、それに対しては
1.自分をここまで追い詰めた管理サイドがアカンのや!!と責任転嫁して、
2.でも周りに迷惑かけてしまってスンマセンと後でお菓子とか差し入れしたりとか、
3.あるいは何らかの形で貢献してくとか、
4.でも「貸し借り」という感覚はナシで。人間、生きる限りは誰かしらに迷惑かけるのだから、「お互い様」でプラマイ0と考えて。
という感じでスルー力を鍛えれば良いと思います。
ホントこの辺は・・・仏教含めて、世界三大宗教であればある程度は自分の中だけで組み立てられるようになってるのですが・・・如何せん、現代日本ではそこまで「お仕事」と人生と宗教のバランスが大分「お仕事」よりに偏ってしまってて、人間の限界に対して絶望しなくて済むよう、華麗に生き延びられるロジックとしての宗教がうまく手を差し伸べられてないように思うんですよね・・・。別に新興宗教まで持ちださなくとも、既成の三大宗教でそれなりにカバー出来るだけの歴史と厚みと蓄積がある筈なんですが・・・。少なくとも仏教には上座部・大乗ともにありますし、他の2大宗教でもカバー出来ると期待してるんですが。
弱くても・・・まぁ強いに越したことは無いのですが、弱くても、弱さを識別して認識して、弱いなりに生きていければ良いのですがね。
会社で働いてスキルアップしなければ何にもなれない人になる、とか。
そんな言葉で他人を急き立てる社会、あんまり気分良くないなぁ。というのが正直な感想です。
自分はこれだけ頑張ってる、というのを世間一般の水準としてしまって、「もっと頑張れるでしょ、もっともっと頑張れるでしょ」と、一時的に引っ張るのは良いのですが、延々と引っ張られていると何と言うか引っ張られる方も気づかれしちゃうでしょうよと。
人間だってナマモノなんですから、休憩も必要でしょうに、と思わなくもないです。
・・・それとも、そんな物言いする人たち、もしかして周りが悪意を持った、あるいは悪意を持たずとも、怠け者ばかりで、それの尻拭いとかにヒドイ思いをしてきたのでしょうか・・・。
Groovy/Gradle/Mixing Java and Groovy でGradleを使ったJava/Groovy混在プロジェクトでのポイントをまとめました。
しかしながら、既存のMaven資産や(ようやく安定し始めた感のある)m2e + Eclipse IDEとの統合開発環境を活用したいいために、引き続きJavaを中心でビルドシステムはMaven、開発効率化としてGroovyで脇を固めたい、というニーズもあるはずです。・・・あるんですってば。
そうした場合にも、2013-04現在では大きく2種類の方式で、Java/Groovy混在プロジェクトをMavenで統合管理出来ます。
Groovy/Maven/GMaven + Eclipse memo ではGMavenをごく簡単に紹介しましたが、今回はより深く突っ込んで、以下のパターンそれぞれで、"groovyc" AntタスクによるビルドとGMavenによるビルドを構成してみます。
というわけで作成したのが以下になります。
細かい解説は、README.mdおよび実際のコードを参照してください。
むしろ本記事では、これらの作成で思いっきり地雷を踏み抜いいたり嵌ってしまった部分を紹介して行きたいと思います。
さらに、おまけとして一番複雑な「Java + GroovyのMavenプロジェクト(Java -> Groovy参照:有り、Groovy -> Java参照:有り)」を題材に、Eclipse + m2e環境でインポートして "mvn tomcat6:run" でJava/Groovyそれぞれをデバッグ実行するためのポイントも簡単に紹介します。
最初に参考サイトです。
GMaven参考
GroovyのAntタスクを、maven-antrun-pluginから呼び出す:
嵌った箇所・・・ではありませんが、そもそも"Java Joint Compile"とはどういったものかについてです。
まず、基本的に xxxx.groovy が yyyy.java ソースのクラスを参照するだけであればどのビルドシステムを使っても大して問題は起きません。
ところが、yyyy.java が zzzz.groovy ソース内のクラスを参照しているとなると、問題が起こります。
大抵のビルドシステムでは・・・というかGradle/Maven/Antでは、特に意識しない限りは、「Javaコンパイル」→「Groovyコンパイル」の順に処理されます。教科書(=公式サイト)の解説にしたがってGroovyコンパイルを追加しただけだと。
すると、yyyy.java をコンパイルするには zzzz.groovy をコンパイルしたクラスが必要ですが、 zzzz.groovy をコンパイルするにはJavaコンパイルフェーズが完了していなければならず、鶏が先か卵が先か、という状況になってしまいます。
これを解決するのが "Java Joint Compile" (念のため:本記事で便宜的につけてる名前で、プラグインや記事によって表記ゆれがあるっぽいです) です。
これは、最初に xxxx.groovy, zzzz.groovy を、クラスやメソッドのシグネチャだけJavaに変換した "Stub" の java ファイルを生成します。これらは戻り値のあるメソッドであればnullを返すだけなど、本当に、単にそれらを参照してる java ファイルのコンパイルを成功させるためだけの内容です。
続いて、それを含めて本来のyyyy.javaがビルドシステムのJavaコンパイルフェーズでコンパイルされます。
最後に、本来の xxxx.groovy や zzzz.groovy が改めてGroovyでコンパイルされます。↑でコンパイルされたStubの同名classファイルは、このステップにより本来のGroovyソースがコンパイルされた正しい内容で上書きされます。
・・・一応上記説明はイメージです。今回色々試行錯誤して、失敗時や成功時のエラーメッセージを見たり途中過程を追ううちに、多分こうなってるんじゃないかな~程度で。
ということで、「まずStubを生成する」という"Java Joint Compile"の特性を理解していないと、GMavenやgroovyc AntTask + maven-antrun-pluginをうまく構成出来ません。
2013-04-27追記:何を間違えていたのか、今日改めて確認してみたら次の設定で問題なく、GMavenのstub生成とcompileが適切なタイミングで実行されました。
<executions> <execution> <goals> <goal>generateStubs</goal> <goal>compile</goal> <goal>generateTestStubs</goal> <goal>testCompile</goal> </goals> </execution> </executions>
GitHubの方には既に上記のようなシンプルな設定に直したpom.xmlを上げてあります。
https://github.com/msakamoto-sf/maven-java-groovy-conjunction-demo1/commit/09109628f6ddd4f4b9c2b7df7e295d25bf7f10a5
以下の手法は、もし上記の設定でうまく動かなかった場合の、手動調整用として参照してください。
Groovy/Maven/Java + TestNG Example で既にGMavenを使ってMavenプロジェクトを構成していましたが、その時点では
mvn groovy:compile groovy:testCompile
というふうに手動でGMavenのゴールを指定する必要がありますよ・・・と書いてました。(2013-04-27:書いてませんでしたね。何を勘違いしてたんだろう・・・)
pom.xmlはこんな感じです:
<plugin> <groupId>org.codehaus.gmaven</groupId> <artifactId>gmaven-plugin</artifactId> <version>${gmavenVersion}</version> <configuration> <providerSelection>${gmavenProviderSelection}</providerSelection> <sourceEncoding>UTF-8</sourceEncoding> </configuration> <executions> <execution> <goals> <goal>generateStubs</goal> <goal>compile</goal> <goal>generateTestStubs</goal> <goal>testCompile</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>${groovyVersion}</version> </dependency> </dependencies> </plugin>
今だから言えますが、これ、単にGMavenの公式Web上のコピペしただけですので、本来のGMavenのゴールを正しく、対応するフェーズで実行する設定にはなってません。2013-04-27に改めて検証したところ、上記設定で問題ないようです。(何か別のところでミスってたか、勘違いしてたか。)
本来は、各ゴールは以下のフェーズで実行されるのが・・・多分、今日試してた限りではgoodぽい。
ということで、上記のマッピングでGMavenのゴールとphaseが連動するように調整したのが、以下になります。
<plugin> <groupId>org.codehaus.gmaven</groupId> <artifactId>gmaven-plugin</artifactId> <version>${gmavenVersion}</version> <configuration> <providerSelection>${gmavenProviderSelection}</providerSelection> <sourceEncoding>UTF-8</sourceEncoding> </configuration> <executions> <execution> <id>gmaven-generate-stubs</id> <phase>generate-sources</phase> <goals> <goal>generateStubs</goal> </goals> </execution> <execution> <id>gmaven-compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>gmaven-generate-test-stubs</id> <phase>generate-test-sources</phase> <goals> <goal>generateTestStubs</goal> </goals> </execution> <execution> <id>gmaven-generate-test-compile</id> <phase>test-compile</phase> <goals> <goal>testCompile</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>${groovyVersion}</version> </dependency> </dependencies> </plugin>
いや~、これに辿り着くまで、改めてMavenのphaseとgoalの関係を勉強しなおしたり、ちょっとした嵌りどころでした。
2013-04-27追記:改めて、本来は前述のような簡略化されたgoal設定で問題ないようです。うまく動かなかったりした時の、手動設定として上記のmappingを参照してください。
groovyc Antタスクでは"<javac>"タスクをネストすることで自動的に"Java Joint Compile"を処理してくれます。
が・・・Mavenに組み込もうとするとちょっとした・・・どころじゃなくて、ドエライ嵌りどころが出てきます。
maven-compiler-pluginのデフォルト挙動をストップする必要があるのが嵌りポイントです。
これ、解決までに5時間を要しました。
groovyc Antタスクは、compileフェーズで起動するようにmaven-antrun-pluginを使って設定されます。
ところが、そうなるとデフォルト状態では"maven-compiler-plugin"によるデフォルトのJavaコンパイルも発動してしまいます。このプラグインによるコンパイルは、Groovyの存在など全く知らないため、 Groovyソースを参照しているJavaソースをコンパイルしようとしたら当然エラーになります。
groovyc Antタスクがサポートしている"Joint Compile"を使わずに、"maven-compiler-plugin"によるコンパイルでJavaソースをコンパイルしようとすると、Stubファイルを予め生成する必要が出てくるわけです。
が・・・「この辺手動で生成すればうまくいけるんじゃねーかなー」と、そう考えていた時代が僕にもありました。
手動でstubを生成して・・・
↑↑この辺使って動的にaddSourceして・・・
その後、maven-compiler-plugin によるデフォルトのJavaコンパイラ起動、さらにその後、groovycのAntタスクを起動でどうにかならね?
・・・と思ったのですが。実験してみればすぐわかるんですが、
という流れで、にっちもさっちも行かなくなります。(ここでさらに独自のantタスクとか準備して、Groovyソースと同名のクラスファイルが生成されていれば一旦それらを削除してからgroovycのAntタスク起動・・・とかやるのもアリなんですが、あまりにもなんというか、力技過ぎる気がするので却下しました。)
そうなると、最終的な回答としてはStubファイルの手動生成 + maven-compiler-pluginではなく、groovyc Antタスクがサポートしているネストされた "<javac>" による "Java Joint Compile" が残された手段になります。
その場合もネックになるのが maven-compiler-plugin の無効化です。というわけで今回最も時間を取られた嵌りどころが・・・
そこでようやく本題ですが、Googleで調べた "disable maven-compiler-plugin" の尽くが全く期待通りに動いてくれなかったんですよ!!!
先に答えを書くと、以下のように"<id>"で "default-compile", "default-testCompile" を指定した上で"<phase>none</phase>"を指定すると、compile/test-compileの各フェーズでmaven-compiler-pluginが無効化されます。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.0</version> <executions> <execution> <id>default-compile</id> <phase>none</phase> </execution> <execution> <id>default-testCompile</id> <phase>none</phase> </execution> </executions> </plugin>
Googleで調べて見つけた記事では、この <id> 指定をしてる記事が見当たらなかったんですよ。(いや、そりゃヒットした上位数件を見ただけなので、細かく見てけばもっとちゃんとしたのもあったとは思うんですが。)
実は、"<id>"指定を省略するとデフォルトの <id> の設定が残ってしまうので、どんなに頑張ってもそれが残ってるので maven-compiler-plugin のデフォルトコンパイルを無効化出来ないというとんでもない罠が潜んでます。
これ、ようやく半信半疑で "mvn help:effective-pom" で見つけるまでに、試行錯誤で5時間費やしました・・・。
なお、環境は
JDK 1.7 Maven 3.4 maven-compiler-plugin 3.0
です。もしかしたら古いバージョンでは"<id>"指定なしでも無効化出来たのかもしれませんね。
では最後のトピックです。
https://github.com/msakamoto-sf/maven-java-groovy-conjunction-demo1
の、"sample6 : 02-java-refer-groovy-antrun" をEclipseにインポートするときの注意点です。
環境:
JDK 1.7 Eclipse 4.2 (Juno SR-2) m2e 1.3.1 Groovy-Eclipse Plugin 2.8.0 (SpringSourceのGGTSでまとめて導入)
そのまま "Existing Maven Projects" でインポートしようとすると、
Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-antrun-plugin...
とエラーが発生します。このエラー自体は後で修正出来るタイプのエラーらしく、インポート自体は完了します。
また、その後プロジェクト右クリック -> "Configure" -> "Convert to Groovy Project" でGroovyサポートを組み込み、ソースディレクトリを手動で調整してあげれば、コンパイルも成功します。
あとは上記のエラーだけですが、この内容は端的に言うと、「pom.xml中で指定されたmaven-antrun-pluginを、Eclipse上でいつ実行すれば良いのか分かりません。」というエラーです。
詳細は以下で解説されてます。
対処法としては、m2e 1.3であれば pom.xml にm2eのlifecycle設定用のmapping情報を埋め込みます。これにより、どのプラグインをEclipse上でどのタイミングで実行すればよいのか、mapping情報が埋め込まれることで、m2eは上記エラーを解決出来ます。
今回のmaven-antrun-plugin設定については、結局のところEclipse上ではEclipseのJavaコンパイラとGroovy-Eclipse PluginによるGroovyプラグインが、自動的に Java Joint Compile も処理してくれるため、Eclipse上では不要となります。そのため、以下のように単に"<ignore />"を"<action>"に設定すればOKです。
<project> ... <build> ... <plugins> ... <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <versionRange>[1.7,)</versionRange> <goals> <goal>run</goal> </goals> </pluginExecutionFilter> <action> <ignore /> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </build> </project>
以上のテクニックを用いて、
https://github.com/msakamoto-sf/groovyservlet-sample-template
に上記を適用したpom.xmlを用い、"mvn tomcat6:run"をデバッグ実行、見事Eclipse上で対話的にブレークポイント設定して、ブレークで止められたのが以下にupした画像になります。
http://twitpic.com/ckyezv
Groovy/Java/Maven/EclipseのIntegrationネタは大体、以上でひと通り出揃ったと思います。
というかこれ以上地雷が潜んでるとか勘弁して欲しいので、ホント、これで終わりにしたいです。
Groovy・・・というか、JVM上で動作する言語(Groovy, Scala, JRuby, Jython, Clojure)は言語それ自体だけでなく、JVMをとりまくエコシステム(ビルドシステム, IDE)をトータルで扱えて初めて、Javaを超えることが出来るという感じがします。
教科書(=公式サイト)には載っていないようなイレギュラーなケースに対応出来て、初めて実際の開発現場でその威力を発揮できるはずです。
ということで、本記事が「言語自体はスゴイのだけれど、既存のMavenで組み上げられたJava中心のプロジェクトに追加するのはちょっと・・・」と感じているエンジニアの方に、その壁をぶち壊す突破口を切り開くお手伝いになることを祈ります。
・・・いやホント、自分が嵌ってプライベート時間を無駄遣いしたのは、他で困ってるエンジニアを救ったという事実が救済してくれますので、何卒どこかで役立つことを祈ってます。
EclipseでGroovy/Grailsを楽しむためのメモ。
2013-04-22 update : Groovy/Maven/Examples (includes "Java Joint Compile") により深く、Maven + GMaven 構成について調査・検証していますので、そちらも参照してください。本記事で紹介・参照している GMaven よりもより実際の開発に即した構成を紹介しています。
Mavenプロジェクトで、Groovyで作成したクラスをコンパイルできるようにする:
参考:
GroovyServletでファイルアップロードを処理させようとしたところ、思いっきり落とし穴に嵌りましたのでメモ。
時間がない方向けの結論:
Gradleのwarプラグインでは、servlet apiの依存性を追加するために
dependencies { ... providedCompile 'javax.servlet:servlet-api:2.5' ... }
という設定が使えるようになっています。
実は、"providedCompile"という依存性の指定はwarプラグイン独自に追加しているもので、通常のjavaプラグインだけのプロジェクトでは使えません。
上記のような、「コンパイルの時はクラスパスに入れるけど、配布用のパッケージングからは外す」タイプの依存性は、Mavenでは"provided" scopeで実現出来ます。
これと同等の仕組みをGradleで使おうとすると、warプラグインを(そのためだけにわざわざ)追加するか、あるいは以下の様なwork-arroundがあります。
※特に手元で検証したわけではないので、参考情報程度で。
こんな感じで、"optional"と"provided"という依存性セットが使えるようになるそうです。
dependencies { compile("commons-logging:commons-logging:1.1.1") optional("log4j:log4j:1.2.17") provided("javax.servlet:javax.servlet-api:3.0.1") testCompile("junit:junit:4.11") }
参考:
configurations { provided } sourceSets { main.compileClasspath += configurations.provided test.compileClasspath += configurations.provided test.runtimeClasspath += configurations.provided } dependencies { provided 'org.apache.ant:ant:1.8.4' }
Eclipse/IDEAなど、IDE側でGradleプロジェクトを読み込んだ時の調整についても考慮する必要があるようです。
依存jarファイルの、ファイルシステム上のフルパスの取得方法についてメモ。
Gradleでは、依存性は階層構造で管理されます。
Project -> ConfigurationContainer -> Configuration : これが最終的に依存性を集約します。
"ConfigurationContainer"は何かというと、依存性のセットを名前をつけて管理してます。
dependencies { compile ... runtime ... testCompile ... }
のように構成した場合、"compile", "runtime", "testCompile"というキーに対してそれぞれのConfigurationインスタンスをマッピングします。
ConfigurationContainer自体はProject.getConfigurations()または単純に"configurations"プロパティからアクセスできます。
これにより、例えば "compile" の依存性のセットに対して何か操作したい場合は、以下のように簡単に依存性のセット(Configuration)にアクセス出来ます。
task foo { configurations.compile.(Configurationのプロパティやメソッド) }
依存するJarファイルのフルパスを取得する手っ取り早い方法は、Configurationクラスのfiles()メソッドを使います。
apply plugin: 'java' repositories { mavenLocal() mavenCentral() } dependencies { compile 'org.apache.commons:commons-lang3:3.1' runtime 'ch.qos.logback:logback-classic:1.0.9' testCompile 'junit:junit:4.11' } task depFiles { configurations.each { println "---------------------------------------------" Configuration theConfig = it println theConfig.name println ">>> dependencies" theConfig.dependencies.each { println it.group + ":" + it.name + ":" + it.version } println ">>> resolved files" theConfig.files.each { // files()メソッドに何も渡さないと、すべてのjarファイルのフルパス // がjava.io.Fileオブジェクトで取得される。 println it } } println "=============================" // files()メソッドの引数に、booleanを返すclosureを指定し、 // 特定のjarファイルのフルパスだけを取得する。 configurations.compile.files({ dep -> dep.name == 'commons-lang3' }).each { file -> println file } }
依存性の管理方法や、アクセス方法などはGradleの公式ドキュメントを一読されるのをおすすめします。
あるディレクトリで、その場で即席のWebサーバ立ちあげられたら便利だなと思いました。
PHPでも5.4になって組み込みサーバが使える様になりましたが、PEARなどの外部ライブラリの連携が最近はどうなってるか知らないので、ちょっとスルー。せっかくなのでGroovyエコシステムが使えたほうが楽しそうなので、やっぱりGroovy + Jettyだよね。
http://php.net/manual/ja/features.commandline.webserver.php
Jettyの前に、winstoneという軽量なServletContainerもありました。が、2008年で開発が止まってるのが微妙。
去年くらいにJetty8をGroovyで使ったことがあるが、GrapeでDLしたのがそのままロードされなくて、".ivy"以下にDLされたXMLファイルか何かを弄ってようやく"@Grab"出来た記憶が。
でも、またバージョンアップされてて、試したら今度はそんなバッドノウハウいらなかった。
groovyserv使うと、場合によってはgroovyserver側にJettyインスタンスが残ってしまう可能性を考慮して、ちゃんとシャットダウンシーケンス作りたいよね。Server.stop()とかServer.setGracefulShutdown()呼べば良さそう。
別ポート番号でHTTP受け付けて、そこからServer.stop()呼べばイインジャネ?
結局のところ、別ポート立ちあげなくとも、"__stop__"という特殊なURLにShutdown専用のServlet紐付けて、そこからServer.stop()呼ぶスレッド(wait中にしとく)をnotify()すればいいよね、ということでfix.
→というわけで Groovy/真・超お手軽Web開発(Groovy-1.8 + Jetty8, 201304版) をUP。そろそろGroovyServletでのサクサク開発のバリエーションでっち上げるの、飽きてきた・・・。4バージョン目じゃなイカ。
あと今更だけれど、SessionってThreadSafeだっけ?という話:
→Servlet2.5位で微妙に仕様が変わってるらしいが、いずれにしても、うっかりするとスレッドセーフでなくなる可能性が高い状況。基本スタンスとしてスレッドセーフ「でない」ものとして扱い、必要に応じて何かしらラッパーを挟む必要がありそう。
mustacheを拡張したHogan.jsをGroovy向けに改良したもの。
mustache:
Hogan.js, Hogan.groovy:
mustacheをJavaおよびGroovyで使うための参考:
実際に自分も練習してみました:
HTML向けのテンプレートエンジンとしてはかなり使いやすく小回りが効く点もポイント高いです。特にデフォルトでHTMLエスケープされる点が素晴らしいと思います。
あとはpartialでマッピング(コンテキスト?)を切り替えられれば、よく使うフォームタグなどを共通部品化出来るのですが、やり方知ってる人いたら教えて欲しいです・・・。Closure使う方式だと、文字列しか渡ってこないので・・・。
重箱の隅をつつくような細かいTips
Hogan.compile()はHoganTemplateインターフェイスの実装クラスを返しますが、デフォルトはGroovyHoganTemplateを継承したクラスのインスタンスが返されます。
そして、GroovyHoganTemplateでは、テンプレートを処理中の結果をインスタンス変数に格納しています。
これによりどのような影響が考えられるかというと、ServletContainer上でHogan.compile()が返すHoganTemplateのインスタンスをキャッシュして、レンダリングに使いまわす場合、同時に同じHoganTemplateのインスタンスのrender()メソッドを呼ぶとレンダリング結果が不正になる可能性があります・・・というか、簡単なスクリプトで実際にそうなります。
hogan_mt1.groovy:
@Grab(group='com.github.plecong', module='hogan-groovy', version='3.0') import com.github.plecong.hogan.* def data = [ m1: "hello1", m2: "hello2", m3: "hello3", 'sleep3': { println Thread.currentThread().getName() sleep(3 * 1000) return { "AWAKEN" } }, ] def template_s = """ {{m1}}{{m1}}{{m1}} {{#sleep3}}sleep now{{/sleep3}} {{m2}}{{m2}}{{m2}} {{#sleep3}}sleep now{{/sleep3}} {{m3}}{{m3}}{{m3}} """ def template = Hogan.compile(template_s) Thread.start { def r = template.render(data) synchronized(template) { println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } } sleep(1000) Thread.start { def r = template.render(data) synchronized(template) { println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } }
レンダリングの途中で、"sleep3"というクロージャで強制的に3秒間スリープさせ、それを2スレッド、1秒空けて並走させてみます。
結果は、以下のように片方のスレッド側に、もう片方の途中までのレンダリング結果が含まれてしまいます。
$ groovy hogan_mt1.groovy: Thread-73 Thread-74 Thread-73 Thread-74 -------------------Thread-73 hello1hello1hello1 hello1hello1hello1 AWAKEN hello2hello2hello2 AWAKEN hello2hello2hello2 AWAKEN hello3hello3hello3 ------------------- -------------------Thread-74 AWAKEN hello3hello3hello3 -------------------
単なるバグで済めばラッキーですが、Web上でアカウント画面など秘密情報をレンダリングするような箇所でこれを使ってしまうと、最悪、他の人向けのレンダリング結果が混入して情報漏えいにつながる可能性も考えられます。
どうしてもHogan.compile()の結果をキャッシュさせたい、となれば、あるスレッドがHoganTemplate.render()を使ってる間は、他のスレッドは待たせる必要があります。
JVM上のマルチスレッドでの排他処理の話題になりますので、色々解法はあると思いますが、単純に思いついたのはHoganTemplateのインスタンスに対してsynchronizedかければ(多分)大丈夫なんじゃないかなーと。
hogan_mt2.groovy:
// 途中まではhogan_mt1.groovyと同じなので省略 Thread.start { synchronized(template) { def r = template.render(data) println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } } sleep(1000) Thread.start { synchronized(template) { def r = template.render(data) println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } }
実行してみると、当たり前ですがHoganTemplate.render()が同期化され、結果が混ざることは無くなりました。
$ groovy hogan_mt2.groovy: Thread-77 Thread-77 -------------------Thread-77 hello1hello1hello1 AWAKEN hello2hello2hello2 AWAKEN hello3hello3hello3 ------------------- Thread-78 Thread-78 -------------------Thread-78 hello1hello1hello1 AWAKEN hello2hello2hello2 AWAKEN hello3hello3hello3 -------------------
HoganTemplateのインスタンスをキャッシュするのを諦め、代わりにHogan.compileClass()したクラスをキャッシュしておき、毎回Hogan.create()でインスタンスを生成します。
Hoganは全体として以下の様な流れになってます。
HoganTemplate自体をキャッシュさせられれば、全体の3/4を最初の1度だけに済ませられるのですが、それですとスレッド排他処理させるときにどうしても同期化が必要になってしまい却ってパフォーマンスが悪くなりそう、ならばHogan.compileClass()までをキャッシュさせ、全体の1/2の工程を最初の1度だけに抑えよう、という方針です。
hogan_mt3.groovy:
// 途中まではhogan_mt1.groovyと同じなので省略 def template_s = """ {{m1}}{{m1}}{{m1}} {{#sleep3}}sleep now{{/sleep3}} {{m2}}{{m2}}{{m2}} {{#sleep3}}sleep now{{/sleep3}} {{m3}}{{m3}}{{m3}} """ Class<HoganTemplate> htclazz = Hogan.compileClass(template_s) def lock = new Object() Thread.start { HoganTemplate t = Hogan.create(htclazz, template_s) def r = t.render(data) synchronized(lock) { println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } } sleep(1000) Thread.start { HoganTemplate t = Hogan.create(htclazz, template_s) def r = t.render(data) synchronized(lock) { println '-------------------' + Thread.currentThread().getName() println r println '-------------------' } }
実行してみると、ひとまずちゃんと分離されてます。
$ groovy hogan_mt3.groovy Thread-83 Thread-84 Thread-83 Thread-84 -------------------Thread-83 hello1hello1hello1 AWAKEN hello2hello2hello2 AWAKEN hello3hello3hello3 ------------------- -------------------Thread-84 hello1hello1hello1 AWAKEN hello2hello2hello2 AWAKEN hello3hello3hello3 -------------------
partialを使いはじめると、使用するpartialについてもスレッドセーフを考慮する必要が出てきますので、より複雑になってくると思われます。
Groovy/超お手軽Web開発(Groovy-1.8 + Jetty8, 201209版)の続き。GroovyServletが微妙にバグfixとかされてて、前回のWindowsプラットフォーム上でのBadKnowHowが不要になり、さらに、Jetty8もマイナーバージョンアップしてて、もうGrabでのインポートで妙な嵌り方をしなくなったということで、Groovy/お手軽Web開発(GroovyServlet, Java, Gradle, Tomcat)でGradle + Tomcatに仕立てたのをもう一度 Jetty にバックポートしてみました。
検証環境:
Win7 SP1 64bit JDK7 64bit Groovy 1.8.9
使い方:
groovy start_jetty.groovy -> http://localhost:8090/
shellscriptとかbatと抱き合わせ、さらにGroovyServにより高速化すると、ほぼ一瞬でお好きなディレクトリをルートにしたWebサーバ(Jetty)を起動出来ます。
".groovy"ファイルをGroovyServletで処理出来ますので、ちょっとしたWebアプリの練習とか実験に使うことを想定しています。GroovyさえインストールしておけばあとはGrapeにより自動的に依存jarをDLしますので、GradleだのTomcatだの用意する必要もありません。
Jettyの停止は Ctrl-C でも大丈夫ですが、"(context path)/__stop__" というURLにアクセスすればシャットダウンするようにしています。
EclipseでGradleプロジェクトを扱うには、Mavenと同様2種類の方法が存在します。
2013-04-06時点での検証環境:
Win7 SP1 64bit JDK7 64bit Gradle 1.5 eclipse-jee-juno-SR2-win32-x86_64 Gradle Integration for Eclipse (Gradle IDE, 3.1.0.201210040512-RELEASE9(全て表示する)