背景
JenkinsというCIツールではGroovyのコードでCI挙動を記述できるJenkins pipelineがあります。 Jenkins pipelineではGroovyのDSL(Domain-Specific Language:ドメイン固有言語)が使われています。 DSLにはクロージャが効果的に使用されています。
今回はクロージャの動作の基本編です。
クロージャとは
クロージャとは、生まれ故郷(クロージャ自身が定義された場所)を忘れない無名関数のことです。
こちらページが詳しいです。
クロージャ内のメソッド呼び出し
生まれ故郷を忘れない無名関数とは何か、以下のプログラムで確認します。
MainクラスのclosureTest()
内でPilpeline
クラスのpipeline
関数にクロージャ({
と}
で囲んだ部分)を渡しています。
(クラス名と関数名はJenkins pipelineを意識しています)
クロージャの内部ではa()
を呼び出しています。a()
はMain
クラス、Pipeline
クラス両方で定義されています。
pipeline()
メソッド内で、渡されたクロージャ(cl
)を実行しています。
class Pipeline { def pipeline(Closure cl) { println "before closure" cl() println "after closure" } def a() { println "I'm Pipeline.a" } } class Main { def closureTest() { def pipeline = new Pipeline() pipeline.pipeline { a() } } def a() { println "I'm Main.a" } } def main = new Main() main.closureTest()
このコードを実行します。出力は以下のようになりました。
before closure I'm Main.a after closure
クロージャが実行されたとき、Main.a()
が呼ばれていることがわかります。
このように、クロージャは呼ばれた場所でなく、定義された(クロージャが書かれた)場所で実行されます。難しくいうと、クロージャは変数名やメソッドの名前解決を、(デフォルトでは)定義された場所で行う、ということです。クロージャを実行する側(今回はPipelineクラス)での名前解決は行われません。
呼び出し元のメソッドは呼ばれるか
確認として、Main.a()
を削除してみましょう。それ以外は上記と同じコードです。
class Pipeline { def pipeline(Closure cl) { println "before closure" cl() println "after closure" } def a() { println "I'm Pipeline.a" } } class Main { def closureTest() { def pipeline = new Pipeline() pipeline.pipeline { a() } } // def a() { // println "I'm Main.a" // } } def main = new Main() main.closureTest()
実行します。エラーになりました。
before closure Exception thrown groovy.lang.MissingMethodException: No signature of method: Main.a() is applicable for argument types: () values: [] Possible solutions: any(), tap(groovy.lang.Closure), any(groovy.lang.Closure), is(java.lang.Object), wait(), wait(long) at Main$_closureTest_closure1.doCall(closure_01.groovy:18) at Main$_closureTest_closure1.doCall(closure_01.groovy) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at Pipeline.pipeline(closure_01.groovy:4) at Pipeline$pipeline.call(Unknown Source) at Main.closureTest(closure_01.groovy:17) at Main$closureTest.call(Unknown Source) at closure_01.run(closure_01.groovy:36) at jdk.internal.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
これにより、クロージャを実行したクラス/メソッド(今回はPipeline.pipeline()
)に定義されたメソッドはクロージャ内からは呼ばれないことが分かりました。