![](/img/trans.png)
[英]SLF4J groovy annotation compilation error. 'log' was found in a static scope but doesn't refer to a local variable, static field or class
[英]Groovy 2.4 variable scope in closure with @Field annotation
有人可以向我解釋為什么在initVars('c')
如果在聲明中使用@Field
則無法修改引用的對象?
import groovy.transform.Field;
@Field def lines4 = "a";
void initVars(String pref){
println('init:'+lines4+' '+pref) //*3.init:a b *7.init:b c
lines4 = pref;
}
println("closure1") ///1. closure1
1.times {
println(lines4) ///2. a
initVars('b') ///3. init:a b
lines4 += 'p1'
println(lines4) ///4. bp1
}
println("closure2") ///5. closure2
1.times {
println(lines4) ///6. bp1
initVars('c') ///7. init:b c
println(lines4) ///8. bp1 Why not c
lines4 += 'q1'
println(lines4) ///9. bp1q1 Why not cq1
}
輸出:
C:\projects\ATT>groovy test.groovy
1. closure1
2. a
3. init:a b
4. bp1
5. closure2
6. bp1
7. init:b c
8. bp1
9. bp1q1
沒有@Field
和def
輸出,在腳本范圍內只有lines4 = "a"
。 這對我來說似乎很正常。
C:\projects\ATT>groovy test.groovy
1. closure1
2. a
3. init:a
4. bp1
5. closure2
6. bp1
7. init:bp1
8. c
9. cq1
我在groovy2.5-beta和groovy 2.6-alpha中看到了相同的行為。
在腳本變量上使用@Field
注釋會將此變量的范圍從本地變量更改為Script
類1:
用於將腳本中的變量范圍從腳本的run方法更改為腳本的類級別的變量注釋。
帶注釋的變量將成為腳本類的私有字段。 字段的類型將與變量的類型相同。 用法示例:
import groovy.transform.Field @Field List awe = [1, 2, 3] def awesum() { awe.sum() } assert awesum() == 6
在這個例子中,沒有注釋,變量awe將是一個本地腳本變量(從技術上講,它將是腳本類的run方法中的局部變量)。 在awesum方法中不會看到這樣的局部變量。 使用注釋,awe成為腳本類中的私有List字段,並在awesum方法中可見。
資料來源: http : //docs.groovy-lang.org/2.4.12/html/gapi/groovy/transform/Field.html
每個Groovy腳本都擴展了groovy.lang.Script
類,腳本的主體在Script.run()
方法中執行。 Groovy使用Binding
對象將變量傳遞給此腳本。 當您將本地腳本變量的范圍更改為類級別時,傳遞給閉包的此變量沒有綁定,因為binding
對象僅包含本地范圍的變量 。 比較我制作的這兩個截圖。 第一個顯示第一次調用initVars(String pref)
時binding
對象的樣子,而lines4
是本地腳本變量:
這里是相同的斷點,但現在lines4
是@Field def lines4
變量:
正如您所看到的, binding
對象中的lines4
變量沒有綁定,但是有一個名為lines4
的類字段,而此綁定存在於附加的第一個屏幕截圖中。
你打電話的時候
lines4 += 'p1'
在第一個閉包中,創建了lines4
局部綁定,並使用this.lines4
值的當前值初始化它。 這是因為Script.getProperty(String property)
以下列方式實現:
public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
資料來源: https : //github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54
因此,它首先檢查是否存在對您在閉包中訪問的變量的綁定以及何時不存在它將執行傳遞給父的getProperty(name)
實現 - 在我們的示例中它只返回類屬性值。 此時this.lines4
等於b
,這是返回的值。
initVars(String pref)
方法訪問類字段,因此當您調用它時,它始終會覆蓋Script.lines4
屬性。 但是當你打電話時
lines4 += 'q1'
在第二個閉包中,閉包的綁定lines4
已經存在,其值為bp1
- 該值在第一個閉包調用中關聯。 這就是為什么你在調用initVars('c')
后沒有看到c
的原因。 我希望它有所幫助。
binding
如何解釋 讓我們更深入一點,以便更好地了解幕后發生的事情。 這是Groovy腳本在編譯為字節碼時的樣子:
Compiled from "script_with_closures.groovy"
public class script_with_closures extends groovy.lang.Script {
java.lang.Object lines4;
public static transient boolean __$stMC;
public script_with_closures();
public script_with_closures(groovy.lang.Binding);
public static void main(java.lang.String...);
public java.lang.Object run();
public void initVars(java.lang.String);
protected groovy.lang.MetaClass $getStaticMetaClass();
}
此時值得一提的兩件事:
@Field def lines4
被編譯成類字段java.lang.Object lines4;
void initVars(String pref)
方法被編譯為public void initVars(java.lang.String);
類方法。 為簡單起見,您可以假設腳本的其余內容(不包括lines4
和initVars
方法)內聯到public java.lang.Objectrun()
方法。
initVars
始終訪問類字段lines4
因為它可以直接訪問此字段。 將此方法反編譯為字節碼向我們顯示:
public void initVars(java.lang.String);
Code:
0: invokestatic #19 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_2
4: aload_2
5: ldc #77 // int 5
7: aaload
8: aload_0
9: aload_2
10: ldc #78 // int 6
12: aaload
13: aload_2
14: ldc #79 // int 7
16: aaload
17: aload_2
18: ldc #80 // int 8
20: aaload
21: ldc #82 // String init:
23: aload_0
24: getfield #23 // Field lines4:Ljava/lang/Object;
27: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
32: ldc #84 // String
34: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
39: aload_1
40: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
45: invokeinterface #52, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callCurrent:(Lgroovy/lang/GroovyObject;Ljava/lang/Object;)Ljava/lang/Object;
50: pop
51: aload_1
52: astore_3
53: aload_3
54: aload_0
55: swap
56: putfield #23 // Field lines4:Ljava/lang/Object;
59: aload_3
60: pop
61: return
操作56是用於為字段分配值的操作碼。
現在讓我們了解當兩個閉包被調用時會發生什么。 首先要提到的是 - 兩個閉包都delegate
字段設置為正在執行的腳本對象。 我們知道它擴展了groovy.lang.Script
類 - 一個使用binding
私有字段來存儲腳本運行時中可用的所有綁定(變量)的類。 這是重要的觀察,因為groovy.lang.Script
類重寫:
public Object getProperty(String property)
public void setProperty(String property, Object newValue)
兩種方法都使用binding
來查找和存儲腳本運行時中使用的變量。 getProperty
讀取本地腳本變量時都會調用getProperty
並且setProperty
為腳本局部變量賦值時都會調用setProperty
。 這就是為什么代碼如下:
lines4 += 'p1'
生成如下序列:
getProperty -> value + 'p1' -> setProperty
在您的示例中,第一次嘗試讀取lines4
最終會從父類返回一個值(如果未找到綁定,則會發生這種情況,然后GroovyObjectSupport.getProperty(name)
,並返回具有給定名稱的類屬性的值)。 當closure為lines4
變量賦值時,則創建綁定。 並且因為兩個閉包共享相同的binding
對象(它們使用委托給同一個實例),當第二個閉包讀取或寫入line4
變量時,它使用先前創建的綁定。 並且initVars
不會修改綁定,因為正如我之前所示,它直接訪問類字段。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.