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和其他基于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, Closure cl) {

        ExpandoMetaClass emc = newExpandoMetaClass(clazz, 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, Closure cl) {

        ExpandoMetaClass emc = newExpandoMetaClass(clazz, 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()

    }

}

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



### 使用 Groovy DSL 进行 Spring Integration 开发 Spring Integration 提供了一种基于 Groovy 的领域特定语言 (DSL),用于简化集成逻辑的定义。通过使用 Groovy 脚本,开发者能够更简洁地配置消息通道、端点和服务。 为了在项目中启用 Groovy DSL 支持,首先需要确保引入了必要的依赖项: ```xml <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-groovy</artifactId> <version>${spring-integration.version}</version> </dependency> ``` 接着可以通过 `GroovyBeanDefinitionReader` 来加载 Groovy 文件中的 Bean 定义[^1]。下面展示了一个简单的例子来说明如何利用 Groovy DSL 创建一个基本的消息流: ```groovy beans { xmlns 'http://www.springframework.org/schema/integration' channel('inputChannel') serviceActivator(inputChannel: 'inputChannel', ref:'myService', method:'handleMessage') bean(id:"myService", class:"com.example.MyServiceImpl") } ``` 上述代码片段展示了如何声明一个名为 `inputChannel` 的输入通道以及关联的服务激活器组件。服务激活器会监听来自指定通道的消息并调用目标对象的方法处理这些消息。 对于希望进一步探索 Groovy 和 Spring 集成特性的开发者来说,Grails 框架提供了一些额外的功能和支持[^2]。此外,在较新的版本中,还可以考虑采用纯 Java 方式的配置方案作为替代选项之一[^3]。 最后值得注意的是,除了传统的 XML 或者注解驱动的方式外,Spring Boot CLI 工具也支持直接执行 Groovy 脚本文件来进行快速原型设计和测试工作[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值