タイトル/名前 | 更新者 | 更新日 |
---|---|---|
日記/2013/03/23/今日の調査メモ : Groovy, Category, Mixin, ClassLoading | msakamoto-sf | 2013-03-23 22:17:55 |
Groovy/TemplateEngine/GroovyTemplates | msakamoto-sf | 2013-03-23 22:03:49 |
日記/2013/03/21/今日の調査メモ : JRebel maker buys Javeleon, Vaadin | msakamoto-sf | 2013-03-21 08:26:02 |
Groovy/お手軽Web開発(GroovyServlet, Java, Gradle, Tomcat) | msakamoto-sf | 2013-03-17 21:17:58 |
日記/2013/03/17/今日の調査メモ : Dynamic Java Class Reloading Libraries (JRebel, Javelelon, Dynamic Code Evolution VM) | msakamoto-sf | 2013-03-17 17:40:10 |
Groovy/Gradle/Tips | msakamoto-sf | 2013-03-17 11:24:19 |
Groovy/Gradle/Mixing Java and Groovy | msakamoto-sf | 2013-03-17 11:23:50 |
Groovy/Scripting/Tips | msakamoto-sf | 2013-03-10 00:40:56 |
Groovy/Debug/Object.dump(), inspect() | msakamoto-sf | 2013-03-09 23:47:51 |
Groovy/GroovyServ メモ | msakamoto-sf | 2013-03-09 23:17:17 |
GroovyのCategoryとMixin周り:
実行時にObject.mixinできるってナニソレ怖い。
他、Groovyのドキュメントに動的にjarやクラスパスを追加する手法の解説があったのでメモ。
Groovyが提供しているテンプレートエンジン:
検証環境:
Win7 64bit JDK 1.7 64bit Groovy 1.8.9
GStringTemplateEngineでは、テンプレートテキストは "groovy.tmp.templates.GStringTemplateScript[N]"([N]にはインクリメントされる数字が入る)のclosure内で処理されます。テンプレート内で
<%= dump() %> <%= inspect() %>
すると以下のように出力されます。
↓↓"<%= dump() %>"の出力 <groovy.tmp.templates.GStringTemplateScript9$_getTemplate_closure1@73b05494 \ delegate=groovy.lang.Binding@3d80fbe2 \ owner=groovy.tmp.templates.GStringTemplateScript9@5f18d1f9 \ thisObject=groovy.tmp.templates.GStringTemplateScript9@5f18d1f9 ... ↓↓"<%= inspect() %>"の出力 groovy.tmp.templates.GStringTemplateScript9$_getTemplate_closure1@73b05494
ということで、
def binding = [ title : "...", c : { s -> ... },
とした場合、テンプレート中で
<%= c(title) %>
としても、「groovy.tmp.templates.GStringTemplateScript[N]にはc()なんてメソッドありません」とエラーになります。
これを回避するには、以下のように"call()"メソッドで明示的にclosureを呼び出します。
<%= c.call(title) %>
例:
@Grapes([ @Grab(group='org.apache.commons', module='commons-lang3', version='3.1'), ]) import org.apache.commons.lang3.* String input_s = "<hello, 'bonjour' & \"evening\">" def binding = [ title : input_s, h1 : { s -> StringEscapeUtils.escapeHtml4(s).replace("'", ''') }, h2 : { s -> s.toString().replace('&', '&').replace('<', '<') .replace('>', '>').replace('"', '"').replace("'", ''') }, ] println binding.h1(input_s) println binding.h2(input_s) def engine = new groovy.text.GStringTemplateEngine() def text = '''\ Raw Title : [<%= title %>] HTML Escaped Title(1) : [<%= h1.call(title) %>] HTML Escaped Title(2) : [<%= h2.call(title) %>] ''' def template = engine.createTemplate(text).make(binding) println template.toString()
実行結果:
<hello, 'bonjour' & "evening"> <hello, 'bonjour' & "evening"> Raw Title : [<hello, 'bonjour' & "evening">] HTML Escaped Title(1) : [<hello, 'bonjour' & "evening">] HTML Escaped Title(2) : [<hello, 'bonjour' & "evening">]
もう一つの方法は、"Object"クラスにテンプレートから使いたいメソッドをmixinしてしまう方法です。
@Grapes([ @Grab(group='org.apache.commons', module='commons-lang3', version='3.1'), ]) import org.apache.commons.lang3.* String input_s = "<hello, 'bonjour' & \"evening\">" class Helper { String h1(String s) { StringEscapeUtils.escapeHtml4(s).replace("'", ''') } String h2(String s) { s.toString().replace('&', '&').replace('<', '<') .replace('>', '>').replace('"', '"').replace("'", ''') } } Object.mixin(Helper) def binding = [ title : input_s, ] println h1(input_s) println h2(input_s) def engine = new groovy.text.GStringTemplateEngine() def text = '''\ Raw Title : [<%= title %>] HTML Escaped Title(1) : [<%= h1(title) %>] HTML Escaped Title(2) : [<%= h2(title) %>] ''' def template = engine.createTemplate(text).make(binding) println template.toString()
実行結果:
<hello, 'bonjour' & "evening"> <hello, 'bonjour' & "evening"> Raw Title : [<hello, 'bonjour' & "evening">] HTML Escaped Title(1) : [<hello, 'bonjour' & "evening">] HTML Escaped Title(2) : [<hello, 'bonjour' & "evening">]
日記/2013/03/17/今日の調査メモ : Dynamic Java Class Reloading Libraries (JRebel, Javelelon, Dynamic Code Evolution VM) で見かけたJRebelとJaveleonだが、先日、JRebel側がJaveleonを買収したみたい。
あと、こんなの見つけた。
JavaScriptとサーバサイドを組み合わせたリッチなインターフェイスのWebアプリ構築スタックらしい。extjsの仲間かと思ったが、サーバサイド側も含んでいるようなのでもっと複雑かもしれない。
Groovy/超お手軽Web開発(Groovy-1.8 + Jetty8, 201209版) でGroovyServletを使った非常にミニマルなWeb開発環境を紹介しました。今回はもう少し掘り下げて、実際にServletやJavaソースを組み合わせ、Tomcat上でclassファイルの自動リロード(Context auto reload)も有効にした「サクサク開発」が出来る構成を作ってみたので、紹介します。
サンプル:
ポイント:gradle-tomcat-plugin を使う + Java/Groovyクラスファイルの出力先を"/WEB-INF/classes/"以下に設定する。
gradle-tomcat-plugin:
検証環境:
Win7 64bit JDK7 64bit Gradle 1.4
以下のように、実際に開発の現場で使いそうなポイントを組み込んでいます。
ということで、ソースの詳細についてはGitHubで眺めていただき、ポイントとなる部分だけを紹介していきます。
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などでの「サクサク開発」と同じです。
$ 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ファイルの中を覗いてみると、以下のようにクラスファイルのエントリが重複してしまっています。
$ 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し、正常に動作することを動作確認しております。
Groovy/Gradle/Mixing Java and Groovy のテクニックを使ってます。以下のように、mainとtestの両方の "SourceSet" でJavaのソース指定を空っぽにして、Groovy側のコンパイラでコンパイルするように調整をしています。
build.gradle:
... [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 = [] } ...
GradleのJavaコンパイルのタスクも、Groovyコンパイルのタスクも、どちらも"Compile"タスクの型から派生しています。ということで、以下のようにすることでJava/Groovyの両方で共通してJavaソースのバージョン, 文字コード, デバッグ情報の埋め込みを設定できます。
tasks.withType(Compile) { sourceCompatibility = '1.7' targetCompatibility = '1.7' options.encoding = 'UTF-8' options.debug = true }
EclipseとGraldeの連携という観点で色々と挑戦されているようです。
今回、"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と比べると大分見劣りがしてしまう・・・。
Javaでもっと動的にクラスをリロードさせたい場合に、ClassLoaderにまつわるもろもろのノウハウが詰め込まれたライブラリやパッケージ等。
JRebel : 有償
JRebel関連記事:
Javeleon : 有償
Dynamic Code Evolution VM : 無償
Gradle使う時の細かいTipsとかメモ書き。
ポイント:JavaからGroovyを参照する時は、Groovy側と一緒にコンパイルされるようにする。
GradleではJavaコンパイラがまず実行され、続いてGroovyコンパイラ(内部的にはGroovyとJavaを一緒にコンパイルできるようになってる)が起動されます。そのため、Groovy側ソースのクラスを参照しているJavaソースがある場合、最初にJavaコンパイラが実行される時点ではGroovyのソースやクラスを認識できないため、参照先のGroovy側のパッケージやクラス名を解決出来ずエラーになります。なお、JavaのソースからGroovyを参照しておらず、あくまでもGroovyがJavaを参照しているだけであれば本記事で紹介しているような調整は不要です。
( http://www.gradle.org/docs/current/userguide/groovy_plugin.html のタスク依存関係参照。)
解決策としては以下の様な対応方法があります。
サンプル:
検証環境:
サンプルコード:
https://github.com/msakamoto-sf/gradle-java-groovy-conjunction-demo1/tree/master/java-refer-groovy-0
Groovyのクラスを "src/main/java" のクラスから参照してます。参照先のGroovy側のパッケージやクラス名を解決出来ずにエラーになります。
"gradle -d"でデバッグログを有効にしてJavaコンパイラが起動するときの引数を確認してみると、当然ですが "src/main/groovy" 関連のパスはどこにも出て来ません。
23:37:22.253 [DEBUG] [org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler] Compiler arguments: \ -d (...)/build/classes/main \ -g \ -classpath (.../.gradle/...)/groovy-all-1.8.9.jar \ (...)/java-refer-groovy-0src/main/java/j1/JavaBean.java \ (...)/java-refer-groovy-0/src/main/java/j1/Main.java
サンプルコード:
https://github.com/msakamoto-sf/gradle-java-groovy-conjunction-demo1/tree/master/java-refer-groovy-1
build.gradleで以下のように調整し、GroovyコンパイラがJavaのソースを一緒にコンパイルしてくれるように調整してます。
sourceSets.main.java.srcDirs = [] sourceSets.main.groovy.srcDirs = ['src/main/groovy', 'src/main/java'] sourceSets.test.java.srcDirs = [] sourceSets.test.groovy.srcDirs = ['src/test/groovy', 'src/test/java']
sourceSets.(main|test).java.srcDirs から、Groovy側でコンパイルさせるJavaソースディレクトリを外しておくのを忘れないようにします。
サンプルコード:
https://github.com/msakamoto-sf/gradle-java-groovy-conjunction-demo1/tree/master/java-refer-groovy-2
"src/main/groovy/" 以下にGroovyとJavaソースを配置してます。これによりJavaコンパイラは何も処理せずにスルーされ、Groovyコンパイラ側で一緒にコンパイルしてくれます。
GroovyScriptとしてファイルに書いたのをそのままgroovyコマンドで実行するときのTips
多分オーソドックスなのは「共通コードはライブラリ化してjarにするだろJK」なのだろうけど、どうせ1-2つのクラスだけなんだろうし、それだけのためにjar作ってclasspathいじるのメンドイ、という時に、groovycでコンパイルしたクラスをそのまま同じディレクトリに放置しておくのもアリだなと思ってこんな感じにしました。
ライブラリ側:lib.groovy
@Grapes([ @Grab(group='org.slf4j', module='slf4j-api', version='1.7.2'), @Grab(group='ch.qos.logback', module='logback-classic', version='1.0.9'), ]) import groovy.util.logging.* @Slf4j class C1 { static void m1() { println 'from m1:' log.trace 'trace2' log.debug 'debug1' log.info 'info1' log.warn 'warn1' log.error 'error1' } } @Slf4j class C2 { static void m2() { println 'from m2:' log.trace 'trace2' log.debug 'debug2' log.info 'info2' log.warn 'warn2' log.error 'error2' } }
これをコンパイルしておきます。
$ groovyc lib.groovy $ ls C1.class C2.class lib.groovy
groovycが"@Grape"も見てくれるのか、依存jarを指定する必要がないのがちょっと感動的です。
で、同じディレクトリにC1, C2を使うメインとなるGroovy Scriptを置きます。
main.groovy:
$ cat main.groovy @GrabConfig(systemClassLoader=true) @Grapes([ @Grab(group='org.slf4j', module='slf4j-api', version='1.7.2'), @Grab(group='ch.qos.logback', module='logback-classic', version='1.0.9'), ]) import groovy.* // dummy C1.m1() C2.m2()
"@Grapes"と本体コードの間にダミーの"import"を入れてますが、これが無いといきなり"C1"で始まるのが、何がイケないのかわかりませんがsyntaxエラーで怒られました。
こっちでも同じ"@Grab"が必要なのと、こちらは実行側なので"@GrabConfig"の調整が必要だったりします。
あとはgroovyでもGroovyServのgroovyclientでも好きな方で実行してください。
$ groovy main.groovy from m1: 00:38:33.909 [main] DEBUG C1 - debug1 00:38:33.913 [main] INFO C1 - info1 00:38:33.914 [main] WARN C1 - warn1 00:38:33.914 [main] ERROR C1 - error1 from m2: 00:38:34.030 [main] DEBUG C2 - debug2 00:38:34.030 [main] INFO C2 - info2 00:38:34.030 [main] WARN C2 - warn2 00:38:34.030 [main] ERROR C2 - error2
共通コードを変更した時はgroovycで再コンパイルが必要だったり、共通コードのファイル名を
xxxx.groovy
としたら、その中で
class Xxxx {
とするとなんかうまく全体として動いてくれないなど微妙な地雷臭はあったりしましたが、一応これはこれでアリといえばアリかなと。
Groovyが拡張してくれたObject.dump()とinspect()が便利。
dump()はJavaのクラス名とか内部プロパティまで詳細情報を文字列で取得出来ます。
inspect()ですとフレンドリーな文字列表現を取得出来ます。
つまるところ、printlnとかロギングでのデバッグ出力で便利です。
例:t_collections.groovy ( http://groovy.codehaus.org/Collections から適当に練習用に抜き出した。)
def c1 = [1, 2, 3] println '------------------------------------' println 'dump() : [' + c1.dump() + ']' println 'inspect(): [' + c1.inspect() + ']' c1 << 4 println '------------------------------------' println 'dump() : [' + c1.dump() + ']' println 'inspect(): [' + c1.inspect() + ']' c1 << [5, 6] << 7 println '------------------------------------' println 'dump() : [' + c1.dump() + ']' println 'inspect(): [' + c1.inspect() + ']' def c2 = [:] println '------------------------------------' println 'dump() : [' + c2.dump() + ']' println 'inspect(): [' + c2.inspect() + ']' c2['name'] = 'abc' c2.age = 20 def m1 = { you -> return "hello, ${you}." } c2.greet = m1 println '------------------------------------' println 'dump() : [' + c2.dump() + ']' println 'inspect(): [' + c2.inspect() + ']' println c2.greet('bob') def c3 = new Expando() c3.name = 'Jon' c3.greet = { "Good morning, ${name}" } println '------------------------------------' println 'dump() : [' + c3.dump() + ']' println 'inspect(): [' + c3.inspect() + ']' println c3.greet();
実行:
$ groovy t_collections.groovy ------------------------------------ dump() : [<java.util.ArrayList@7861 elementData=[1, 2, 3] size=3 modCount=1>] inspect(): [[1, 2, 3]] ------------------------------------ dump() : [<java.util.ArrayList@e93c3 elementData=[1, 2, 3, 4] size=4 modCount=2>] inspect(): [[1, 2, 3, 4]] ------------------------------------ dump() : [<java.util.ArrayList@36b936e8 elementData=[1, 2, 3, 4, [5, 6], 7] size=6 modCount=4>] inspect(): [[1, 2, 3, 4, [5, 6], 7]] ------------------------------------ dump() : [<java.util.LinkedHashMap@0 header=null=null accessOrder=false table=[null] size=0 threshold=0 loadFactor=0.75 modCount=0 useAltHashing=false hashSeed=-1854095574 entrySet=[] keySet=null values=null>] inspect(): [[:]] ------------------------------------ dump() : [<java.util.LinkedHashMap@2029ac47 header=null=null accessOrder=false table=[greet=t_collections$_run_closure1@1a16ff7a, age=20] size=3 threshold=1 loadFactor=0.75 modCount=3 useAltHashing=false hashSeed=-1854095574 entrySet=[name=abc, age=20, greet=t_collections$_run_closure1@1a16ff7a] keySet=null values=null>] inspect(): [['name':'abc', 'age':20, 'greet':t_collections$_run_closure1@1a16ff7a]] hello, bob. ------------------------------------ dump() : [<groovy.util.Expando@4c48b2f8 expandoProperties=[name:Jon, greet:t_collections$_run_closure2@38dddee8]>] inspect(): [{name=Jon, greet=t_collections$_run_closure2@38dddee8}] Good morning, Jon
公式 : http://kobo.github.com/groovyserv/index.html
インストーラもありますが、win用のバイナリのzipファイルが配布されてますので、そちらを展開して環境変数を調整するだけでもOKです。というかそちらの方法しか試してません。
自分はCygwin使いですので、Cygwinの.bashrcでこんな感じにしてます。
# Groovy GROOVY_HOME=/cygdrive/c/work/apps/groovy-1.8.9 export PATH=$GROOVY_HOME/bin:$PATH # GroovyServ GROOVYSERV_HOME=/cygdrive/c/work/apps/groovyserv-0.11 export PATH=$GROOVYSERV_HOME/bin:$PATH alias gr='groovyclient'
"GROOVYSERV_HOME"は本来は不要で、binにpathが通ってればオッケーなんですが、見た目が揃ってるので入れてます。また、"groovyclient"もタイピングが長いので"gr"にalias設定してます。
まずgroovyserverを起動します。
$ groovyserver Groovy home directory: /cygdrive/c/in_vitro/devtools/groovy-1.8.9 Groovy command path: /cygdrive/c/in_vitro/devtools/groovy-1.8.9/bin/groovy (found at GROOVY_HOME) GroovyServ home directory: /cygdrive/c/in_vitro/devtools/groovyserv-0.11 GroovyServ work directory: /home/FengJing/.groovy/groovyserv Original classpath: (none) GroovyServ default classpath: /cygdrive/c/in_vitro/devtools/groovyserv-0.11/lib/* Starting.... groovyserver 224(1961) is successfully started
あとはGroovyスクリプトを書いて、groovyclientで実行するだけです。
groovyserverを止めたい場合は"-k"付きで実行します。
$ groovyserver -k Groovy home directory: /cygdrive/c/in_vitro/devtools/groovy-1.8.9 Groovy command path: /cygdrive/c/in_vitro/devtools/groovy-1.8.9/bin/groovy (found at GROOVY_HOME) GroovyServ home directory: /cygdrive/c/in_vitro/devtools/groovyserv-0.11 GroovyServ work directory: /home/FengJing/.groovy/groovyserv Original classpath: (none) GroovyServ default classpath: /cygdrive/c/in_vitro/devtools/groovyserv-0.11/lib/* Killed groovyserver of 224(1961)
簡単ですね。たったこれだけの手間で、Groovyスクリプトの実行が爆速になります。
通常はgroovyclientによる実行のたびにクラスローダが生成されて"@Grab"で依存関係が読み込まれます。クラスのstaticフィールドなんかもそうですが、つまり、通常は実行のたびにクラスローダがリセットされると考えてよいでしょう。
ただし、当然groovyserver側でシステムクラスローダなどで読み込まれたクラスは残ります。
それで注意が必要なのが、"@GrabConfig(systemClassLoader=true)"を使う場合です。これを使うと、"@Grab"したのが(多分)groovyserver側のシステムクラスローダに読み込まれます。ということは、初回にロードされたものが以降もずっと残ることになります。
この挙動は、システムクラスローダで読み込まれたライブラリが設定ファイルを読み込むときに、初回に読み込まれた設定がずっと残り続け、設定ファイルを書き換えても反映されないという挙動につながります。slf4j + logbackでlogback.groovyを読み込む場合が該当したりしてます。
この場合は、groovyserverを再起動する他ありません。
$ groovyserver -r Groovy home directory: /cygdrive/c/in_vitro/devtools/groovy-1.8.9 Groovy command path: /cygdrive/c/in_vitro/devtools/groovy-1.8.9/bin/groovy (found at GROOVY_HOME) GroovyServ home directory: /cygdrive/c/in_vitro/devtools/groovyserv-0.11 GroovyServ work directory: /home/FengJing/.groovy/groovyserv Original classpath: (none) GroovyServ default classpath: /cygdrive/c/in_vitro/devtools/groovyserv-0.11/lib/* Killed groovyserver of 6672(1961) Restarting groovyserver Starting.... groovyserver 7936(1961) is successfully started
"@Grab"使えるの?とか、クラスのstaticフィールドどうなるの?とかは、GroovyServのFAQページにあったりします: