領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Groovy 語言是建立領域特定語言 (DSL) 的絕佳平臺。一個好的 DSL 可以讓程式更簡潔、更具表現力,並提高程式設計師的生產力。然而,到目前為止,這些 DSL 在編輯器中並沒有得到 Groovy-Eclipse 的直接支援。當 DSL 大量使用時,內容輔助、搜尋、懸停和導航等標準 IDE 功能就會失去其價值。長期以來,編寫 Eclipse 外掛來擴充套件 Groovy-Eclipse 是可能的,但這是一種重量級的方法,需要專門的 Eclipse API 知識。現在,Groovy-Eclipse 支援 DSL 描述符 (DSLD),支援 Groovy-Eclipse 中的自定義 DSL 將變得明顯容易。
3.m + 2.yd + 2.mi - 1.km
這是一個簡單而富有表現力的 DSL,但是當你在 Groovy-Eclipse 中的 Groovy 編輯器中輸入這些內容時(為簡潔起見,假設 $url 在其他地方定義)
[caption id="attachment_8774" align="aligncenter" width="179"]
[/caption]
你會看到下劃線,沒有懸停,這意味著編輯器無法靜態解析 DSL 的表示式。使用 DSLD,可以教編輯器這些自定義 DSL 背後的一些語義,併為懸停提供文件
[caption id="attachment_8775" align="aligncenter" width="683"]
[/caption]
要為距離 DSL 建立 DSL 描述符,你只需在 Groovy 專案中新增一個檔案,副檔名為 .dsld,內容如下
currentType( subType( Number ) ).accept {
property name:"m", type:"Distance",
doc: """A <code>meter</code> from <a href="$url">$url</a>"""
}
此指令碼表示,每當編輯器中當前正在評估的型別是 java.lang.Number 的子型別時,向其新增型別為 Distance 的“m”屬性。currentType(subType(Number)) 部分稱為 切入點,而呼叫 property 的程式碼塊稱為 貢獻塊。稍後會詳細介紹這些概念。
上面這個指令碼片段不是完整的 DSLD。它只添加了“m”屬性。要完成實現,你可以充分利用 Groovy 語法的強大功能
currentType( subType( Number ) ).accept {
[ m: "meter", yd: "yard", cm: "centimerter", mi: "mile", km: "kilometer"].each {
property name:it.key, type:"Distance",
doc: """A <code>${it.value}</code> from <a href="$url">$url</a>"""
}
}
這個簡單的例子表明,一個相對較小的指令碼可以建立一些強大的 DSL 支援。
DSLD 增強了 Groovy-Eclipse 的型別推斷引擎,該引擎在編輯時在後臺執行。DSLD 由 IDE 評估,並在必要時由推斷引擎查詢。
DSLD 指令碼包含一組切入點,每個切入點都與一個或多個貢獻塊相關聯。切入點大致描述了型別推斷需要增強的位置(即,在哪些上下文中增強哪些型別),而貢獻塊描述了如何增強(即,應該新增哪些屬性和方法)。
許多切入點可用,它們在DSLD 文件中詳細描述並附有示例。隨著我們開始瞭解人們將如何建立指令碼以及他們需要什麼樣的操作,可用切入點的集合可能會在 DSLD 的未來版本中擴充套件。
貢獻塊是透過 accept 方法與切入點關聯的 Groovy 程式碼塊。您可以在貢獻塊中執行的兩個主要操作是 property(我們之前介紹過)和 method(它向貢獻塊中分析的型別新增一個方法)。
“切入點”一詞借用自面向切面程式設計 (AOP)。實際上,DSLD 可以被認為是一種 AOP 語言。DSLD 和典型的 AOP 語言(如 AspectJ)之間的主要區別在於,DSLD 對正在編輯的程式的抽象語法樹進行操作,而像 AspectJ 這樣的語言對已編譯程式的 Java 位元組碼進行操作。
Codehaus 維基上提供了完整的 DSLD 文件。在這裡,我將簡要介紹如何開始使用 DSLD。要開始
http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.6/
currentType(subType('groovy.lang.GroovyObject')).accept {
property name : 'newProp', type : String,
provider : 'Sample DSL',
doc : 'This is a sample. You should see this in content assist for all GroovyObjects:<pre>newProp</pre>'
}
在 DSLD 內部,您應該會看到特定於 DSLD 的內容輔助和懸停(這來自第 2 步中新增的元 DSLD 指令碼)。它看起來像這樣:
this.newProp
您應該會看到 newProp 被正確高亮顯示,並且懸停將顯示來自 DSLD 的文件,它看起來應該像這樣:
您可以從 Groovy -> DSLD 首選項頁面檢視和管理工作空間中的所有 DSLD:
在這裡,您可以啟用/停用單個指令碼,並選擇要編輯的指令碼。
重要提示:由於在實現 DSLD 時查詢和修復錯誤可能有點神秘,因此強烈建議您執行以下操作
指令碼的編譯和執行時問題將顯示在這兩個地方之一。
舉一個更大的例子,我們來看看 Grails 框架。Grails 約束 DSL 提供了一種宣告式方法來驗證 Grails 領域類。它清晰簡潔,但如果沒有對該 DSL 的直接編輯支援,Grails 程式設計師就依賴外部文件,並且可能直到執行時才意識到語法錯誤。我們可以建立一個 DSLD 來解決這個問題
// only available in STS 2.7.0 and above
supportsVersion(grailsTooling:"2.7.0")
// a generic grails artifact is a class that is in a grails project, is not a script and is in one of the 'grails-app' folders
def grailsArtifact = { String folder ->
sourceFolderOfCurrentType("grails-app/" + folder) &
nature("com.springsource.sts.grails.core.nature") & (~isScript())
}
// define the various kinds of grails artifacts
def domainClass = grailsArtifact("domain")
// we only require domainClass, but we can also reference other kinds of artifacts here
def controllerClass = grailsArtifact("controllers")
def serviceClass = grailsArtifact("services")
def taglibClass = grailsArtifact("taglib")
// constraints
// The constraints DSL is only applicable inside of the static "constraints" field declaration
inClosure() & (domainClass & enclosingField(name("constraints") & isStatic()) &
(bind(props : properties()) & // 'bind' props to the collection of properties in the domain class
currentTypeIsEnclosingType())).accept {
provider = "Grails Constraints DSL" // this value will appear in content assist
// for each non-static property, there are numerous constraints "methods" that are available
// define them all here
for (prop in props) {
if (prop.isStatic()) {
continue
}
if (prop.type == ClassHelper.STRING_TYPE) {
method isStatic: true, name: prop.name, params: [blank:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [creditCard:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [email:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [url:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [matches:String], useNamedArgs:true
} else if (prop.type.name == Date.name) {
method isStatic: true, name: prop.name, params: [max:Date], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Date], useNamedArgs:true
} else if (ClassHelper.isNumberType(prop.type)) {
method isStatic: true, name: prop.name, params: [max:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [scale:Number], useNamedArgs:true
} else if (prop.type.implementsInterface(ClassHelper.LIST_TYPE)) {
method isStatic: true, name: prop.name, params: [maxSize:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [minSize:Number], useNamedArgs:true
}
method isStatic: true, name: prop.name, params: [unique:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [size:Integer], useNamedArgs:true
method isStatic: true, name: prop.name, params: [notEqual:Object], useNamedArgs:true
method isStatic: true, name: prop.name, params: [nullable:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [range:Range], useNamedArgs:true
method isStatic: true, name: prop.name, params: [inList:List], useNamedArgs:true
}
}
如果您複製上述 DSLD 指令碼並將其新增到 Grails 專案中的 DSLD 檔案中,STS 將會學習約束語言。例如,在以下簡單域類中,您會在約束塊內獲得內容輔助:
上述指令碼可以調整以新增自定義文件。
儘管大多數 Groovy 和 Grails 使用者不實現自己的 DSL,但他們使用 DSL(在 Grails、Gaelyk 中,透過 構建器 等)。因此,儘管大多數 STS 使用者不會建立自己的 DSLD,但他們將受益於他人建立的 DSLD。我們將與庫和 DSL 開發人員密切合作,為 Groovy 生態系統的不同部分建立通用 DSLD。
您可以期望在 Groovy-Eclipse 的即將釋出的版本中看到對流行的基於 Groovy 的框架的支援顯著增加。
DSLD 語言的核心實現現已可用,但我們將根據對使用者需求以及他們希望支援的 DSL 型別的更多瞭解進行調整。我們將實現更多的切入點,擴充套件文件,並努力將一些標準 DSLD 與 Groovy-Eclipse 本身一起釋出。
請嘗試此處或維基上介紹的一些 DSLD,並透過此部落格文章、我們的問題跟蹤器或Groovy-Eclipse 郵件列表向我們提供反饋。