A Groovy DSL from scratch
研究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() ??? } } 后面的事情一样画葫芦就可以了。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |