加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

A Groovy DSL from scratch

发布时间:2020-12-14 16:54:03 所属栏目:大数据 来源:网络整理
导读:研究DSL 时发现这篇文章不错,顺手翻译了一下。原文地址?A Groovy DSL from scratch in 2 hours 今天是我的幸运日 , 我在 Dzone 发现了 Architecture Rules 。这是一个对 Jdepend 进行抽象的一个有趣的小框架。 ? Architecture Rules 有它自己的 Xml Schema

研究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和其他基于GroovyDSL一样使用了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()

??? }

}

后面的事情一样画葫芦就可以了。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读