自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Groovy 類型檢查擴展,使用擴展

開發(fā) 前端
要獲得helper方法的完整列表,請參考類org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport和類org.codehaus.groovy.transform.stc.TypeCheckingExtension。

1. 介紹

在上一篇介紹了基本的Groovy的類型檢查擴展,以及該擴展的意義和部分的API說明。

本篇接著上篇沒有講完的內(nèi)容,繼續(xù)介紹類型檢查擴展的相關知識點。

2. 使用類型檢查擴展

我們講解了如何創(chuàng)建類型檢查擴展,這里開始講解各種的使用方式。

2.1 支持類-Support classes

DSL依賴于一個名為org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport的支持類。這個類本身擴展了org.codehaus.groovy.transform.stc.TypeCheckingExtension。這兩個類定義了許多幫助器方法,使使用AST變得更容易,特別是在類型檢查方面。要知道的一件有趣的事情是,我們可以訪問類型檢查器。這意味著我們可以以編程方式調(diào)用類型檢查器的方法,包括那些允許拋出編譯錯誤的方法。

擴展腳本委托給org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport類,這意味著我們可以直接訪問以下變量:

  • context:類型檢查器上下文,類型為org.codehaus.groovy.transform.stc.TypeCheckingContext
  • typeCheckingVisitor:類型檢查器本身,一個org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor實例
  • generatedMethods:一個“生成方法”的列表,這實際上是一個“dummy”方法的列表,你可以使用newMethod調(diào)用在一個類型檢查擴展中創(chuàng)建。

類型檢查上下文包含大量在上下文中對類型檢查器有用的信息。例如,當前的封閉方法調(diào)用堆棧、二進制表達式、閉包等等,如果我們必須知道錯誤發(fā)生時我們在哪里以及我們想要處理它,那么這些信息就特別重要。

除了GroovyTypeCheckingExtensionSupport和StaticTypeCheckingVisitor提供的功能外,類型檢查DSL腳本還從org.codehaus.groovy.ast.ClassHelper和org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport導入靜態(tài)成員,允許通過OBJECT_TYPE、STRING_TYPE、THROWABLE_TYPE等對公共類型進行訪問,并檢查諸如missesgenericsttypes(ClassNode)、isClassClassNodeWrappingConcreteType(ClassNode)等。

2.2 類節(jié)點-Class nodes

在使用類型檢查擴展時,需要特別注意處理類節(jié)點。編譯使用抽象語法樹(AST),當您檢查類的類型時,該樹可能不完整。這也意味著在引用類型時,不能使用String或HashSet等類字面量,而是使用表示這些類型的類節(jié)點。這需要一定程度的抽象和理解Groovy如何處理類節(jié)點。為了簡化工作,Groovy提供了幾個輔助方法來處理類節(jié)點。例如,如果你想說“String的類型”,你可以這樣寫:

assert classNodeFor(String) instanceof ClassNode

還會注意到,classNodeFor有一個變體,它以String而不是Class作為參數(shù)。一般來說,我們不應該使用該方法,因為它將創(chuàng)建一個名稱為String的類節(jié)點,但沒有在其上定義任何方法、任何屬性等。第一個版本返回已解析的類節(jié)點,而第二個版本返回未解析的類節(jié)點。所以后者應該留給非常特殊的情況。

可能遇到的第二個問題是引用一個尚未編譯的類型。這種情況發(fā)生的頻率可能比你想象的要高。例如,當一起編譯一組文件時。在這種情況下,如果你想說“那個變量是Foo類型的”,但Foo還沒有編譯,仍然可以使用lookupClassNodeFor引用Foo類節(jié)點:

assert lookupClassNodeFor('Foo') instanceof ClassNode

2.3 類型檢查器的幫助

假設你知道變量foo的類型是Foo,你想告訴類型檢查器。然后可以使用storeType方法,該方法接受兩個參數(shù):第一個參數(shù)是希望存儲類型的節(jié)點,第二個參數(shù)是節(jié)點的類型。如果查看storeType的實現(xiàn),我們將看到它委托給類型檢查器等效方法,該方法本身做了大量工作來存儲節(jié)點元數(shù)據(jù)。還將看到,存儲類型并不局限于變量:可以設置任何表達式的類型。

同樣,獲取AST節(jié)點的類型只需調(diào)用該節(jié)點上的getType即可。這通常是你想要的,但有一些注意事項:

  1. getType返回一個表達式的推斷類型。這意味著對于聲明為Object類型的變量,它不會返回Object的類節(jié)點,而是在代碼的這一點返回該變量的推斷類型(流類型)。
  2. 如果想訪問變量(或字段/參數(shù))的原始類型,那么必須在AST節(jié)點上調(diào)用適當?shù)姆椒ā?/li>

2.4 拋出錯誤

要拋出一個類型檢查錯誤,你只需要調(diào)用addStaticTypeError方法,它有兩個參數(shù):

  • 將顯示給最終用戶的字符串形式的消息
  • 負責錯誤的AST節(jié)點。最好提供最合適的AST節(jié)點,因為它將用于檢索行號和列號

2.5 isXXXExpression表達式

通常需要知道AST節(jié)點的類型。為了可讀性DSL提供了一個特殊的isXXXExpression方法,該方法將委托給XXXExpression的x實例。例如:

不建議的寫法:

if (node instanceof BinaryExpression) {
...
}

正確,推薦的寫法:

if (isBinaryExpression(node)) {   ... }

2.6 虛擬方法-Virtual methods

當我們執(zhí)行動態(tài)代碼的類型檢查時,可能經(jīng)常會遇到這樣的情況:知道一個方法調(diào)用是有效的,但它背后并沒有“真正的”方法。

以Grails動態(tài)查找器為例。可以有一個由名為findByName(…)的方法組成的方法調(diào)用。

由于bean中沒有定義findByName方法,類型檢查器會報錯。

但是,我們知道這個方法在運行時不會失敗,甚至可以知道這個方法的返回類型是什么。

對于這種情況,DSL支持由虛擬方法組成的兩個特殊構造。這意味著將返回一個實際上并不存在但在類型檢查上下文中定義的方法節(jié)點。有三種方法:

  • newMethod(String name, Class returnType)
  • newMethod(String name, ClassNode returnType)
  • newMethod(String name, Callable<ClassNode> return Type)

所有這三個方法都做同樣的事情:它們創(chuàng)建一個新的方法節(jié)點,其名稱是提供的名稱,并定義該方法的返回類型。

此外,類型檢查器會將這些方法添加到generatedMethods列表中。我們只設置名稱和返回類型的原因是,這是在90%的情況下所需要的。

例如,在上面的findByName示例中,只需要知道findByName不會在運行時失敗,并且它返回一個域類。

返回類型的Callable版本很有趣,因為當類型檢查器實際需要返回類型時,它推遲了返回類型的計算。

在某些情況下,當類型檢查器要求返回類型時,可能不知道實際的返回類型,因此可以使用閉包,每當類型檢查器在此方法節(jié)點上調(diào)用getReturnType時,都會調(diào)用閉包。

如果將此與延遲檢查結合起來,就可以實現(xiàn)相當復雜的類型檢查,包括前向引用的處理。

newMethod(name) {
//每次調(diào)用這個方法節(jié)點上的getReturnType時,這個閉包就會被調(diào)用!
println 'Type checker called me!'
lookupClassNodeFor(Foo) //返回類型
}

如果你需要的不僅僅是名稱和返回類型,你可以自己創(chuàng)建一個新的MethodNode。

2.7 范圍-Scoping

范圍在DSL類型檢查中非常重要,這也是為什么我們不能使用基于切入點的方法來進行DSL類型檢查的原因之一。

基本上,必須能夠非常精確地定義何時應用擴展,何時不應用擴展。此外,必須能夠處理常規(guī)類型檢查器無法處理的情況,例如前向引用:

point a(1,1)
line a,b // b是事后引用的!
point b(5,2)

例如,你想處理一個構建器:

builder.foo {
bar
baz(bar)
}

因此,我們的擴展應該只在輸入foo方法時是活動的,并且在此范圍之外是不活動的。

但是,可能會遇到復雜的情況,比如同一個文件中有多個構建程序或嵌入式構建程序(構建程序中的構建程序)。

雖然不應該嘗試從一開始就修復所有這些問題(必須接受類型檢查的限制),但類型檢查器確實提供了一種很好的機制來處理這個問題:使用newScope和scopeExit方法的作用域堆棧。

  • newScope :創(chuàng)建一個新的作用域并將其放在堆棧頂部
  • scopeExits :從堆棧中彈出作用域

范圍包括:

  • 父作用域
  • 自定義數(shù)據(jù)的Map

如果想看一下實現(xiàn),它只是一個LinkedHashMap (org.codehaus.groovytypecheckingextensionsupport.typecheckingscope),但是它非常強大。例如,可以使用這樣的作用域來存儲退出作用域時要執(zhí)行的閉包列表。這是處理前向引用的方式:

def scope = newScope()
scope.secondPassChecks = []
//...
scope.secondPassChecks << { println 'executed later' }
// ...
scopeExit {
secondPassChecks*.run() // 執(zhí)行延遲檢查
}

也就是說,如果在某個時候無法確定表達式的類型,或者此時無法檢查賦值是否有效,仍然可以稍后進行檢查……這是一個非常強大的功能。

現(xiàn)在,newScope和scopeExit提供了一些有趣的語法糖:

newScope {
secondPassChecks = []
}

在DSL中的任何時候,都可以使用getCurrentScope()或更簡單的currentScope訪問當前作用域:

//...
currentScope.secondPassChecks << { println 'executed later' }
// ...

一般的模式是:

  • 確定將新作用域推入堆棧的切入點,并在此作用域中初始化自定義變量
  • 使用各種事件,可以使用存儲在自定義范圍中的信息來執(zhí)行檢查、延遲檢查……
  • 確定退出范圍的切入點,調(diào)用scopeExit并最終執(zhí)行額外的檢查

2.8 其他有用的方法

要獲得helper方法的完整列表,請參考類org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport和類org.codehaus.groovy.transform.stc.TypeCheckingExtension。但是,要特別注意以下方法:

  • isDynamic: 接受VariableExpression作為參數(shù),如果變量是DynamicExpression則返回true,這意味著在腳本中,它不是使用類型或def定義的。
  • isGenerated:接受MethodNode作為參數(shù),并告知該方法是否是由類型檢查器擴展使用newMethod 方法生成的方法
  • isAnnotatedBy: 接受一個AST節(jié)點和一個類(或ClassNode),并告知該節(jié)點是否用這個類標注。例如: isAnnotatedBy(node, NotNull)
  • getTargetMethod: 接受一個方法調(diào)用作為參數(shù),并返回類型檢查器為其確定的 MethodNode
  • delegatesTo: 模擬@DelegatesTo注解的行為。它允許我們判斷參數(shù)將委托給特定類型(也可以指定委托策略)

3. 小結

關于類型檢測擴展的使用相關知識要點,就介紹到這里了。以上相關內(nèi)容可以參考Groovy官方文檔:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#Typecheckingextensions-Workingwithextensions進行查詢。

責任編輯:武曉燕 來源: zinyan
相關推薦

2023-01-05 08:09:27

GroovyDSL?

2022-01-10 10:48:34

SorbetVS CodeRuby

2012-07-02 10:43:49

JVMGroovyJava

2012-07-12 11:23:07

GroovyJVM

2020-11-27 20:02:17

LVM邏輯卷管理器

2015-04-20 10:06:37

PHP Rust 創(chuàng)建PHP 擴展

2009-09-10 16:32:19

LINQ Where

2024-07-01 12:13:44

2009-08-27 18:04:01

c#擴展方法string

2023-09-27 11:39:07

GNOMELinux

2021-11-29 10:24:56

WasmEnvoy 負載均衡

2023-12-12 07:30:54

IstioWasm前端

2011-12-26 10:49:27

JavaJUnitRunner

2009-12-04 15:43:03

PHP JSON擴展

2011-12-26 10:38:28

JavaJUnitRule

2009-08-13 18:00:48

Eclipse重構功能擴展點

2022-06-20 14:59:14

讀寫分離模Loki

2015-06-05 09:50:53

LTE

2009-03-16 09:16:13

行為擴展WCF.NET

2009-08-31 14:45:10

C#擴展方法
點贊
收藏

51CTO技術棧公眾號