しもむブログ

技術メモ。といっても高度なことは書けないので…初心者がハマったところや、ググってもすぐには出てこないようなものを書くつもり。

PropertiesからConfigSlurperに乗換えようとしてハマった話

はじめに

  • ConfigSlurperはGroovyスクリプト形式で書かれた設定ファイルを読み込むためのユーティリティクラスです。*1
  • Groovy使い始め直後にConfigSlurperを使おうとしてハマった話です。
  • ちなみにJavaではpropertiesファイルをよく使っています。

Propertiesのとき

  • config.propertiesファイルを用意します。
hoge = ほげ
hoge.foo = ほげふー
  • 読み込んで出力してみます。
class PropertiesSample {
    public static void main(String... args) {
        def properties = new Properties()
        def file = new File("config.properties")
        file.withReader("UTF-8", {
            properties.load(it)
        })

        println properties.getProperty("hoge")
        println properties.getProperty("hoge.foo")
    }
}
  • 実行結果
ほげ
ほげふー
  • 想定通りです。

ConfigSlurperのとき

  • config.groovyを用意します。
  • Groovyスクリプトなのでpropertiesファイルとはちょっと変わります。
hoge = 'ほげ'
hoge.foo = 'ほげふー'
  • 読み込んで出力してみます。
class ConfigSlurperSample {
    public static void main(String... args) {
        def config = new ConfigSlurper().parse(new File('config.groovy').text)

        println config.hoge
        println config.hoge.foo
    }
}
  • エラーになりました。MissingPropertyException。そんなプロパティはない??しかも、fooがないだと!?
Exception in thread "main" groovy.lang.MissingPropertyException: No such property: foo for class: java.lang.String
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:50)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:482)
    at script14811957302811365876559.run(script14811957302811365876559.groovy:2)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at org.codehaus.groovy.reflection.CachedMethod$invoke.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)
    at groovy.util.ConfigSlurper$_parse_closure5.doCall(ConfigSlurper.groovy:266)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod.invoke(ClosureMetaMethod.java:80)
    at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1108)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
    at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:149)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:39)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
    at groovy.util.ConfigSlurper.parse(ConfigSlurper.groovy:284)
    at groovy.util.ConfigSlurper$parse$0.callCurrent(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
    at groovy.util.ConfigSlurper.parse(ConfigSlurper.groovy:168)
    at groovy.util.ConfigSlurper$parse.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at ConfigSlurperSample.main(ConfigSlurperSample.groovy:9)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

結論

  • config.groovyのkeyの命名がイケてませんでした。hogehoge.fooのような親と子両方に値は設定できません。
  • config.groovyをクロージャで書いてみるとなんとなーく理由が想像つくような…
// hogeの値はどうやって設定する?
hoge {
    foo = 'ほげふー'
}
// hoge.fooの値はどうやって設定する?
hoge = 'ほげ'
  • あちらを立てればこちらが立たず状態になってしまいますね…

終わりに

  • Propertiesの感覚のままConfigSlurperのkeyを命名してしまうにはちょっと注意が必要です。
  • ただそれを差し引いたとしてもConfigSlurperはめちゃくちゃ便利です。
  • 明日はligunさんです。よろしくお願いします!