研究DSL 时发现这篇文章不错,顺手翻译了一下。原文地址 A Groovy DSL from scratch in 2 hours
今天是我的幸运日,我在Dzone发现了 Architecture Rules。这是一个对 Jdepend 进行抽象的一个有趣的小框架。
Architecture Rules 有它自己的Xml Schema定义,
<architecture>
<configuration>
<sourcesno-packages="exception">
<sourcenot-found="exception">spring.jar</source>
</sources>
<cyclicaldependencytest="true"/>
</configuration>
<rules>
<ruleID="beans-web">
<comment>
org.springframework.beans.factory cannot depend on
org.springframework.web
</comment>
<packages>
<package>org.springframework.beans.factory</package>
</packages>
<violations>
<violation>org.springframework.web</violation>
</violations>
</rule>
<ruleID="must-fail">
<comment>
org.springframework.orm.hibernate3 cannot depend on
org.springframework.core.io
</comment>
<packages>
<package>org.springframework.orm.hibernate3</package>
</packages>
<violations>
<violation>org.springframework.core.io</violation>
</violations>
</rule>
</rules>
</architecture>
基于configuration API ,两个小时内我就写出了自己的DSL
architecture {
// cyclic dependency check enabled bydefault
jar "spring.jar"
rules {
"beans-web" {
comment ="org.springframework.beans.factory cannot depend onorg.springframework.web"
'package'"org.springframework.beans"
violation"org.springframework.web"
}
"must-fail" {
comment ="org.springframework.orm.hibernate3 cannot depend onorg.springframework.core.io"
'package'"org.springframework.orm.hibernate3"
violation"org.springframework.core.io"
}
}
}
现在我开始说明我是如何构造这个DSL的,这样你也可以学会如何编写你自己的DSL.
这个DSL和其他基于Groovy的DSL一样使用了Builder 语法:方法调用使用一个闭包作为参数。闭包既可以看做一个函数,也可以看做一个对象。你可以像函数一样执行它,也可以像对象方法调用或属性设置的方式执行它。
为了支持这种 builder 语法,你需要写一个方法,该方法使用 groovy.lang.Closure 作为最后一个参数。
//builder 语法示例
someMethod {
}
//一个如下签名的方法将会被调用
// 返回值可以是 voID 或者是 object,由你自己决定
voIDsomeMethod(Closure cl) {
// do some other work
cl() // call Closure object
}
下面第一步就是就是创建一个类用来执行DSL配置文件。我将类名取做 GroovyArchitecture
classGroovyArchitecture {
static voID main(String[] args) {
runArchitectureRules(newfile("architecture.groovy"))
}
static voID runArchitectureRules(file dsl){
Script dslScript = newgroovyshell().parse(dsl.text)
}
}
GroovyArchitecture 类会校验 DSL 文件并生成一个 groovy.lang.Script 对象。如果这个类启动并执行 main 方法,它就会读取当前目录的 architecture. groovy 文件。
现在我们有了一个代码骨架,我们可以继续添加被DSL调用的第一个方法,architecture()
第一个方法的实现是最容易出错的,因为这个方法是在脚本执行时由脚本对象(Sctipt)调用。显然这个对象并没有一个architecture()方法,但Groovy提供了通过 MOP 和Meta-Object 协议的方式动态添加方法。
一个很简单的技术用文字描述起来却非常困难。每一个Groovy对象都有一个MetaClass对象,它负责处理所有向这个Groovy发起的方法调用。 我们所需要做的就是创建一个定制的MetaClass并且赋予这个脚本对象。
classGroovyArchitecture {
static voID main(String[] args) {
runArchitectureRules(newfile("architecture.groovy"))
}
static voID runArchitectureRules(file dsl){
Script dslScript = newgroovyshell().parse(dsl.text)
dslScript.MetaClass =createEMC(dslScript.class,{
ExpandoMetaClass emc ->
})
dslScript.run()
}
static ExpandoMetaClass createEMC(Classclazz,Closure cl) {
ExpandoMetaClass emc = newExpandoMetaClass(clazz,false)
cl(emc)
emc.initialize()
return emc
}
}
我们一步步的分析一下这段代码。我们添加了一个 createEMC方法,它有一个参数是一个闭包。createEMC创建了一个groovy.lang.ExpandoMetaClass 对象,初始化并返回这个对象。ExpandoMetaClass对象实例初始化之前还被传递给了闭包。
这个闭包被当做一个回调函数,用来定制这个ExpandoMetaClass,同时隐藏它被创建和配置的细节。CreateEMC的返回值被赋予了DSLScript Object 的MetaClass属性。
我们也调用了。DSLScript Object 的 run()方法,启动脚本的执行。
Groovy 1.1 之后开始有了 ExpandoMetaClass 类型,通过它我们可以按照 meat-Object协议向一个对象动态添加新的方法。换句话说,通过向一个对象的MetaClass属性赋值一个新的ExpandoMetaClass实例,我们可以给这个对象添加任何我们想要的方法。
现在的问题是,如何添加方法?我们需要配置 ExpandoMetaClass 对象。
classGroovyArchitecture {
static voID main(String[] args) {
runArchitectureRules(newfile("architecture.groovy"))
}
static voID runArchitectureRules(file dsl){
Script dslScript = newgroovyshell().parse(dsl.text)
dslScript.MetaClass =createEMC(dslScript.class,{
ExpandoMetaClass emc ->
emc.architecture = {
Closure cl ->
}
})
dslScript.run()
}
static ExpandoMetaClass createEMC(Classclazz,false)
cl(emc)
emc.initialize()
return emc
}
}
我们给ExpandomeatClass 对象的architecture属性赋予了一个闭包。这个闭包实现了 architecture() 方法,也会接受一样的参数。通过对 architecture 属性赋值,我们就将这个方法加入了DSL 脚本对象,方法原型就是:archutecture(Closure)
这样,我们就可以正确的执行 如下的 DSL 脚本。
// architecture.groovyfile
architecture {
}
下一步就开始加入 Acthitecture 规则类。
importcom.seventytwomiles.architecturerules.configuration.Configuration
importcom.seventytwomiles.architecturerules.services.CyclicRedundancyServiceImpl
importcom.seventytwomiles.architecturerules.services.RulesServiceImpl
classGroovyArchitecture {
static voID main(String[] args) {
runArchitectureRules(newfile("architecture.groovy"))
}
static voID runArchitectureRules(file dsl){
Script dslScript = newgroovyshell().parse(dsl.text)
Configuration configuration = newConfiguration()
configuration.doCyclicDependencyTest =true
configuration.throwExceptionWhennopackages = true
dslScript.MetaClass =createEMC(dslScript.class,{
ExpandoMetaClass emc ->
emc.architecture = {
Closure cl ->
}
})
dslScript.run()
newCyclicRedundancyServiceImpl(configuration)
.performCyclicRedundancyCheck()
newRulesServiceImpl(configuration).performRulestest()
}
static ExpandoMetaClass createEMC(Classclazz,false)
cl(emc)
emc.initialize()
return emc
}
}
Configuration 类为 Architecture Rule 框架设置配置信息,我们使用了两个缺省的值。
下一步是执行脚本中指定jar文件本地位置的动作,我们希望 DSL 脚本类似下面的代码:
//architecture.groovy file
architecture {
classes "target/classes"
jar "mylibrary.jar"
}
添加这两个方法更简单,因为我们不需要再通过 ExpandoMetaClass ,而是设置一个委托(delegate)到闭包对象,也就是architecture()方法执行时做为参数传递进去的那个闭包。
设置委托对象之前,我们还要先创建一个新的类型,ArchitectureDelegate. 对闭包中任何方法或属性的调用都会被 ArchitectureDelegate对象收到,它会提供两个方法 :classes(String) and jar(String).
importcom.seventytwomiles.architecturerules.configuration.Configuration
classArchitectureDelegate {
private Configuration configuration
ArchitectureDelegate(Configurationconfiguration) {
this.configuration = configuration
}
voID classes(String name) {
this.configuration.addSource newSourceDirectory(name,true)
}
voID jar(String name) {
classes name
}
}
可以看到,classes() 和 jar() 都是 ArchitectureDelegate 类实际的方法。下一步就是把这个委托类的实例设置到对应的闭包。
importcom.seventytwomiles.architecturerules.configuration.Configuration
importcom.seventytwomiles.architecturerules.services.CyclicRedundancyServiceImpl
importcom.seventytwomiles.architecturerules.services.RulesServiceImpl
classGroovyArchitecture {
static voID main(String[] args) {
runArchitectureRules(newfile("architecture.groovy"))
}
static voID runArchitectureRules(file dsl){
Script dslScript = newgroovyshell().parse(dsl.text)
Configuration configuration = newConfiguration()
configuration.doCyclicDependencyTest =true
configuration.throwExceptionWhennopackages = true
dslScript.MetaClass =createEMC(dslScript.class,{
ExpandoMetaClass emc ->
emc.architecture = {
Closure cl ->
cl.delegate = newArchitectureDelegate(configuration)
cl.resolveStrategy =Closure.DELEGATE_FirsT
cl()
}
})
dslScript.run()
newCyclicRedundancyServiceImpl(configuration)
.performCyclicRedundancyCheck()
newRulesServiceImpl(configuration).performRulestest()
}
static ExpandoMetaClass createEMC(Classclazz,Closure cl) {
ExpandoMetaClass emc = newExpandoMetaClass(clazz,false)
cl(emc)
emc.initialize()
return emc
}
}
Closure 类型的 delegate 属性接受 ArchitectureDelegate 对象。
The resolveStrategy property 设置为 Closure.DELEGATE_FirsT . 其含义就是让方法和属性的调用被委托到 ArchitectureDelegate 对象。
现在可以为 DSL 增加 rules() 方法:
//architecture.groovy file
architecture {
classes "target/classes"
jar "mylibrary.jar"
rules {
}
}
这个方法应该加到哪里呢?加到 ArchitectureDelegate 中。
importcom.seventytwomiles.architecturerules.configuration.Configuration
classArchitectureDelegate {
private Configuration configuration
ArchitectureDelegate(Configurationconfiguration) {
this.configuration = configuration
}
voID classes(String name) {
this.configuration.addSource newSourceDirectory(name,true)
}
voID jar(String name) {
classes name
}
voID rules(Closure cl) {
cl.delegate = newRulesDelegate(configuration)
cl.resolveStrategy =Closure.DELEGATE_FirsT
cl()
}
}
后面的事情一样画葫芦就可以了。
总结以上是内存溢出为你收集整理的A Groovy DSL from scratch全部内容,希望文章能够帮你解决A Groovy DSL from scratch所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)