Groovy是一种基于JVM,功能强大、类型可选、动态的,可与Java无缝衔接的编程语言,接触Groovy是因为Gradle。而Gradle好用是因为Groovy,到底谁成就了谁呢?
Groovy强大之处,在于创建DSL,Domain Specific Languages。
我们看见的Gradle脚本中的书写规则,就是一种DSL语言
今天我们就来实现一个迷你版DSL语言,了解一下Groovy和DSL
我们把这个项目叫做Bradle吧
build.bradle
先创建一个build.bradle文件,和常见的gradle脚本一样:
repositories { // Use Maven Central for resolving dependencies. mavenCentral() } dependencies { // Use JUnit test framework. testImplementation 'junit:junit:4.13.2' // This dependency is used by the application. implementation 'com.google.guava:guava:30.1.1-jre' } tasks.register("t1"){ println "task t1 executed" } tasks.register("t2"){ doLast{ println "task t2 executed" } }
脚本中的关键字,语法,以及嵌套关系,形成了一个微型的语法规则,这就是我们的DSL。
Script Class
现在我们创建一个类,来对应脚本中的各个关键字,实现DSL的解析,其中task是我们的重点,我们希望能够正确执行task中的语句。
Groovy创建的DSL中,大括号前面的关键字repositories和dependencies其实代表函数调用,后面的大括号,是一种参数类型,叫做Closure。
funcitionName{ closure }
除了函数调用,DSL中还包括属性,属性可以赋值,也可以调用属性对应的方法,咱们例子中,tasks就是属性,register是tasks对应的方法。
此外,Closure中的mavenCentral, testImplementation等,也是函数调用,我们一步步会涉及到
我们定义一个Class,文件名是Project.groovy:
class TaskContainer{ void register(String name, Closure cl){ println name + ":tasks.register get called" } void run(String taskName){ println "Get to run task:" + taskName } } abstract class Project extends Script{ TaskContainer tasks Project(){ tasks = new TaskContainer() } void repositories(Closure cl){ println "repositories get called" } void dependencies(Closure cl){ println "dependencies get called" } }
当我们在build.bradle脚本中写:
repositories { ... }
我们希望是在调用Project.groovy中的函数
void repositories(Closure cl){ ... }
要实现这点,首先Project要继承于Script类。groovy中的脚本要属于Script类,才会被正确解析。其次我们需要把我们的Project类和build.bradle连接起来
Groovyshell
把Project类和build.bradle连接在一起的方式之一,是通过Groovyshell。
我们创建一个新的文件gshell.groovy,调用Groovyshell,串联脚本和对应类:
import org.codehaus.groovy.control.CompilerConfiguration import Project def config = new CompilerConfiguration() //这里设置脚本对应的类 config.scriptBaseClass = 'Project' def shell = new GroovyShell(this.class.classLoader, config) //读取脚本 def script = new File("build.bradle") //把脚本和Project类对应起来,返回Project的一个实例 def projInstance = shell.parse(script) //Project中的run函数继承自Script,开始执行脚本 projInstance.run()
现在我们的目录结构如下:
$ tree . . ├── Project.groovy ├── build.bradle └── gshell.groovy
运行以下检查结果:
$ groovy gshell.groovy repositories get called dependencies get called t1:tasks.register get called t2:tasks.register get called
传递参数
我们还需要传递参数,我们调用gradle运行脚本的时候,格式是gradle taskname这样。我们朝这个方向前进。
首先在gshell.groovy中接受参数:
import org.codehaus.groovy.control.CompilerConfiguration import Project def config = new CompilerConfiguration() config.scriptBaseClass = 'Project' def shell = new GroovyShell(this.class.classLoader, config) def script = new File("build.bradle") def projInstance = shell.parse(script) projInstance.run() //处理命令行参数 taskName=this.args[0] projInstance.tasks.run(taskName)
其次我们用一个脚本封装一下groovy命令,显得更像gradle。
我们创建一个bradle文件:
#!/bin/sh groovy gshell.groovy $1
现在我们的文件目录结构如下:
. ├── Project.groovy ├── bradle ├── build.bradle └── gshell.groovy
运行一下我们新鲜的bradle程序,随便执行一个任务:
$ ./bradle t1 repositories get called dependencies get called t1:tasks.register get called t2:tasks.register get called Get to run task:t1
小结
需要说明的是,本文只是一个示例,并非严谨的生产代码,很多异常情况并未检测,主要目的是为了利用Groovy描述创建DSL和脚本的解析流程。
本文中,所有的函数调用都没有具体实现 ,仅仅是输出了日志信息,下一节我们根据本文的框架,进一步完善Project.goovy类,阐述closure的执行,代理,让我们的task能够运行起来。
本文的例子放在了github上:
Ref
- https://www.jorgemanrubia.com/2009/10/10/evaluating-code-dynamically-in-groovy/
回复 agodelo 取消回复