领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Groovy 语言是创建领域特定语言 (DSL) 的优秀平台。好的 DSL 可以使程序更加简洁和富有表现力,同时提高程序员的生产力。然而,直到现在,这些 DSL 在编辑器中还没有得到 Groovy-Eclipse 的直接支持。当大量使用 DSL 时,内容辅助、搜索、悬停提示和导航等标准 IDE 功能就会失去价值。虽然现在已经可以编写 Eclipse 插件来扩展 Groovy-Eclipse,但这是一种重量级的方法,需要对 Eclipse API 有专门的了解。现在,Groovy-Eclipse 支持 DSL 描述符 (DSLDs),在 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))
部分称为 *pointcut*(切入点),调用 property
的代码块称为 *contribution block*(贡献块)。稍后将详细介绍这些概念。
上面的脚本片段不是完整的 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 脚本包含一组切入点(pointcuts),每个切入点都与一个或多个贡献块(contribution blocks)相关联。切入点大致描述了需要增强类型推断的*位置*(即哪些上下文中的哪些类型),而贡献块描述了*如何*增强(即应该添加哪些属性和方法)。
有许多可用的切入点,在DSLD 文档中有详细描述和示例。随着我们开始了解人们将如何创建脚本以及他们需要哪种操作,可用切入点的集合可能会在 DSLD 的未来版本中扩展。
贡献块是 Groovy 代码块,通过 accept
方法与切入点相关联。在贡献块内部可以执行的两个主要操作是 property
(我们之前已经介绍过)和 method
(向贡献块中正在分析的类型添加方法)。
*切入点(pointcut)*一词借用了面向切面编程 (AOP)。实际上,DSLD 可以被视为一种 AOP 语言。DSLD 与像 AspectJ 这样的典型 AOP 语言的主要区别在于,DSLD 操作的是正在编辑的程序的抽象语法树,而像 AspectJ 这样的语言操作的是已编译程序的 Java 字节码。
在 Codehaus 的 wiki 上有完整的 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 类型,对其进行调整。我们将实现更多的切入点,扩展文档,并努力在 Groovy-Eclipse 本身中附带一些标准的 DSLD。
请尝试使用此处或 wiki 上介绍的一些 DSLD,并通过此博客文章、我们的问题跟踪器或Groovy-Eclipse 邮件列表向我们提供反馈。