DSL设计编程

来源:互联网 发布:mysql开放3306端口 编辑:程序博客网 时间:2024/05/25 18:12

让开发自动化: 使用 Raven 构建 Java 项目

告别表达乏力,使用 Ruby 构建 Java 应用程序

developerWorks文档选项将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Paul Duvall, CTO, Stelligent Incorporated

2007 年 11 月 22 日

Ant 无疑是用于 Java™ 平台的标准构建工具;但是,其它一些构建工具也可以为 XML 提供它一直所缺乏的更具表达性的范例。在这一期的 让开发自动化 中,自动化专家介绍了构建在 Ruby 之上的构建平台 Raven,它可以利用功能全面的编程语言和以构建为中心的域特定语言(Domain Specific Language)的简单性。

我手头上有一个 Java 项目,在过去几年的时间里,我小心翼翼地为之维护一个 Ant 构建脚本。Ant 能够执行大量任务,这一点我十分欣赏;然而,我常常发现,Ant 脚本的 XML 语法编写起来有些麻烦。而且,在 可表达性 方面,Ant 的 XML 部分还存在限制。实际上,当我发现自己需要更高程度的灵活性(例如在条件逻辑方面)时,我常常不得不在 Ant 的 script 任务中编写(例如使用 Groovy)定制任务或嵌入式逻辑。

告别表达乏力

关于本系列
作为开发人员,我们的工作就是为终端用户实现过程自动化;然而,很多开发人员却忽略了将自己的开发过程自动化的机会。为此,我编写了 让开发自动化 这个系列的文章,专门探讨软件开发过程自动化的实际应用,并教您何时 以及如何 成功地应用自动化。

数年前,当我在寻找可表达性更强的软件构建工具时,我听说过 Rake。Rake 是用于使用功能全面的 Ruby 编程语言构建软件的一种域特定语言。通过 Rake,我可以轻松获得正在寻找的可表达性。例如,我不必编写定制任务,因为 Rake 构建脚本是 Ruby(而不是 XML);因此,如果我需要某种条件逻辑,那么很容易在需要的地方编写这种逻辑。而且,Rake 采用基于依赖的任务系统,这类似于 Ant 及其前辈 make 中的任务系统。例如,如果输入 rake deploy,Rake 将调用之前设置的依赖任务,例如编译,运行测试等等。而且 Rake 非常智能,它只运行一次依赖性任务(就像 Ant 一样)。

对于大多数 Java 项目来说,Rake 本身并不能提供多大帮助;然而,一个相对较新的项目 Raven 将 Rake 的功能和可表达性与用于构建 Java 应用程序的特定任务结合起来。有了 Raven,Java 开发人员就有了一个真正可行的构建平台,该平台可提供特定于 Java 的特性,例如使用 javac 进行编译以及使用 jarwar 归档二进制文件,同时还拥有 Ruby 编程语言的丰富的可表达性。

什么是 DSL?
域特定语言(DSL)是专门为特定任务设计的编程语言。 Java 语言和 Ruby 是通用的编程语言,而 Ant 和 Rake 是专用于构建软件的 DSL。Rake 被认为是一种内部 DSL,因为它是用 Ruby 编写的,并且可以使用 Ruby 进行扩展。而 Ant 是一种外部 DSL,因为它首先被解析,然后在另一种语言中执行(也就是说,首先解析 XML,然后通过 Java 程序来运行)。

Raven 是 Rake,Rake 是 Ruby

在将 Raven 用于 Java 项目之前,理解 Raven 与 Rake 之间的关系会有所帮助。图 1 显示了 Raven 的一个逻辑图,可以看到,Raven 使用 Rake、RubyGems 和 Ruby。相应地,在使用 Raven 时,可以使用本地 Rake 任务。而且,还可以在 Raven 脚本中使用 Ruby。RubyGems 是类似于 CPAN、RPM 或 APT 的一个包管理器,用于下载必需的 Ruby 包(以及 Java jar)和任何依赖包。


图 1. Raven 的逻辑图
Raven 的逻辑架构

虽然还不确定 Raven 是不是构建 Java 项目的最佳 平台,但是可以发现,在转向更自然的编程语言方面,Raven 提供了一个简单而强大的组合。要是让我来说,单凭不必依赖 XML 就可构建 Java 项目这一点,足以使 Raven 获得更多关注。





回页首

下载和安装 Raven

有两种方法可安装和配置 Raven,第一种方法是 Ruby 方法,第二种方法则更倾向于 Java,即使用 JRuby。我推荐 Java 方法,因为这样更容易配置。安装 Raven 和使之运行需要以下步骤:

  • 下载 Raven/JRuby 发行版(参见 参考资料)。
  • 将文件解压到工作站上的一个位置。
  • 创建环境变量 JRUBY_HOME
  • JRUBY_HOME/bin 添加到 PATH 环境变量中。
  • 打开一个命令提示符,输入 jruby -version,以检查安装情况。如果完全安装成功,那么在命令窗口中应该看到类似于 ruby 1.8.5 (0) [java] 的内容。

开始使用 Raven

当然,下载和安装 Raven 只是小菜一碟。真正有趣的是在创建构建脚本时 Raven 具有的灵活性。在 Raven 术语中,构建文件被称作 Rakefiles,它们有一个简单的格式。实际上,如果您用 Ruby 编写过程序(或者见过 Ruby 代码),那么这些文件看上去会很熟悉。这正是 Raben 的美妙之处:它就是 Ruby — 只不过在其中添加了一些特殊的词。

为了尽快使用 Raven ,毫无疑问,首先我要执行最基本(但是也很重要)的任务,即编译。图 2 中的目录结构表示了我的 Java 项目的布局。如果知道第三方库和源代码所在的位置,就能更快上手了。


图 2. 现有 Java 项目的目录结构
现有 Java 项目的目录结构

注意图 2 中的 lib 结构,第三方 JAR 就在其中。在开始编译之前,必须创建一个路径,使用 Raven 做这件事再容易不过了,因为它支持通过它的 dependency 任务(实际上是 Raven 提供的一个名为 dependency 的 Ruby 方法)创建类路径,如清单 1 所示:


清单 1. 使用 Raven 创建类路径
require 'raven'require 'rake'dependency 'deps' do | task |  task.libs = Dir.glob('lib/**/*.*')  end 

根据清单 1 中的定义,deps 依赖任务使用 Dir 类和 glob() 方法包括 lib 目录中的所有库。还可以使用这个依赖任务(通过 RubyGems)从一个储存库中下载 JAR(这类似于 Maven 的依赖管理机制)。

在清单 1 中,我特意在路径中包括了 lib 目录中的所有东西。我将这个任务命名为 deps,在尝试编译任何文件之前,都可以显式地执行该任务,如清单 2 所示:


清单 2. 编译很容易
javac 'compile' => 'deps' do |task|  task.build_path << "src"end

可以看到,我通过 Raven 的 javac 任务执行编译。该任务名为 compile,它对清单 1 中的 deps 任务存在依赖。符号 => 的意思是,Raven 将在当前任务之前 运行该操作符右边的任务(这很像 Ant 中 target 元素的 depends 属性。

魔力就发生在 javac 任务的 doend 之间。默认情况下,javac 任务使用 "src/main/java" 作为 build_path 的默认值。但是,由于我的 Java 项目并没有遵循这个惯例(还记得 图 2 吗?),我需要修改 build_path 属性,在这里它应该为 src,因为源代码就在其中。

Raven 只是用于 Maven 的 Ruby 吗?

您可能会认为 Raven 只是 Ruby 版的 Maven,但是这么说并不准确。虽然在默认的命名惯例和依赖管理方面有相似之处,但 Raven 的创始人的解释是,这样做更容易。

为了测试 Raven 脚本,需要打开一个命令提示符,然后输入:rake compile。编译后会生成 .class 文件,这些文件被放到一个名为 target/classes 的目录中,该目录是由 javac Raven 任务自动生成的。

少就是多

这里要弄清楚一点。我不必编写很多代码就可以完成编译,因为 Raven 在幕后处理了一些事情,例如创建 target/classes 目录并将生成的类文件复制到这个位置。实际上,您可能已经忘了 清单 1清单 2 中对应的 Ant 脚本是什么样子。这里提示一下,清单 3 显示了一个典型的使用 Ant 构建脚本的编译:


清单 3. 使用 Ant 编译 Java 源代码
<?xml version="1.0" encoding="iso-8859-1"?><project name="compile-code" default="all" basedir=".">...  <target name="compile-src">    <mkdir dir="${classes.dir}"/>    <javac destdir="${classes.dir}" debug="true">      <src path="${src.dir}" />      <classpath refid="project.class.path"/>    </javac>  </target><project>

将清单 3 与 清单 1清单 2 比较,可以看出一些明显的不同。Raven 不但废除了 XML,而且还假定一些基本的属性,使您在定义任务时需要指定的东西更少。而且,如清单 2 所示,如果有必要,Raven 允许重写假定的属性。

虽然从比较的角度来看清单 1 和清单 2 确实比较有趣,但编译只是构建脚本的有趣之处的一个开始。让我们来看一个更有趣的特性:打包 Java Web 应用程序。





回页首

简单的 war 文件生成

Web 归档(Web Archive,WAR)文件用于打包 Web 应用程序,其中包含诸如 servlet、第三方库和图像之类的各种资源。这些归档文件使用标准的目录命名模式,并且只接受某些文件,例如 web.xml(该文件映射 servlet 名称和其它配置方面),放置在 WEB-INF 目录中。

Raven 使 WAR 文件的创建变得非常简单。在清单 4 中,我以 'brewery.war' 作为名称使用 war 任务,默认情况下,该任务创建一个具有相同名称的 WAR 文件。webapp_dir 的默认位置是 src/main/webapp,但是由于我的目录结构不同于 Raven 的假定,因此需要通过 task.webapp_dir = 'src/web' 行对此进行修改。


清单 4. 创建部署到 Web 容器的 WAR
war 'brewery.war' => ['clean', 'compile', 'java-doc'] do |task|  task.webapp_dir = 'src/web'end

注意,在创建 WAR 文件之前,Raven 必须首先运行 clean 任务,接着运行 compile 任务,然后再运行一个任务来创建一些文档。在这些任务成功运行之后,Raven 可以创建一个归档文件,您将看到,这里需要输入的东西很少。实际上,如果我的项目遵循 Raven 假定的默认目录布局的话,那么除了那些依赖任务之外,不再需要编写任务东西(也就是说,不需要 doend 之间的代码!)。

不管您信不信,Raven 就这么简单 — Raven 的 war 任务处理目录的创建,文件的放置,以及 war 文件的生成。非常高效,不是吗?

是 Raven、Rake 还是 Ruby?
所有都是!Rake 是使用 Ruby 构建项目的一种 DSL。Raven 则使用 Rake 来为构建 Java 项目提供特定于 Java 的功能。就像 Rake 一样,通过 Raven 可以在构建脚本中充分使用 Ruby 编程语言。

轻松地生成文档

生成项目文档对于向团队成员宣传有用信息通常会有所帮助。如果别人计划使用您正在编写的框架代码或实用程序,那么您最终需要发布 Javadoc,这些 Javadoc 是描述相关源代码中的类、方法和公共实例变量的一些 HTML 文件。

通过 Raven,可以很快地利用 Javadoc 机制生成文档,如清单 5 所示:


清单 5. 使用 Raven 生成 JavaDoc 文档
javadoc 'java-doc' => 'deps' do |task|  task.build_path << "src"end

请再次注意,由于我的项目没有遵循 Raven 假定的模型,因此必须修改 javadoc 任务的 build_path 属性。此外,该任务依赖于 deps 任务(在 清单 1 中)来包括所有类引用。

至此,您大概已经意识到,如果项目遵循 Raven 假定的布局,那么构建脚本甚至比我的还要简单!





回页首

更多特性!

虽然我只谈到了 Raven 中提供的一部分特性,但是还可以用这个创新的构建平台做更多的事情。特别地,Raven 为使用 RubyGems 机制处理构建依赖(即第三方库)提供了广泛的支持,RubyGems 在构建时自动下载版本化的二进制文件,而不是引用共享驱动。RubyGems 以类似于 Maven 或 Ivy 的方式处理第三方库。

除了 RubyGems 之外,Raven 还可以运行 JUnit 测试,构建一般的 JAR 文件,并且还可以清理目录。表 1 列出了 Raven 中一些较流行的、可方便构建 Java 项目的任务:


表 1. 流行(并且有用)的 Raven 任务
Raven 任务描述javac 编译 Java 源文件,默认情况下将生成的类文件放入 target/classes 目录中。jar 生成一个 JAR 文件,该文件通常是 .class 文件的一个集合。war 生成一个可部署到 Java Web 容器的 Web 应用程序归档文件。javadoc 根据源代码生成 Javadoc。junit 执行 JUnit 测试。jar_source 根据 Java 源文件创建一个 JAR 文件。gem_wrap_inst 将 JAR 文件转换到 RubyGem,然后将该文件安装到一个存储库中。

随着 Raven 的成熟,表 1 中的列表会进一步扩大。此外,如果 Raven 中还缺少某种您需要的功能,您完全可以自己编写它!





回页首

结束语

本文演示了 Raven 的强大功能,其美妙之处在于,您可以在构建脚本中利用 Ruby 语言的强大功能和灵活性。Raven 是否会成为构建 Java 项目的流行平台不是我能决定的,但是我相信 Raven 是在推动业界朝着正确的方向前进。特别是,Raven 通过一种功能全面的命令式编程语言(而不是像 XML 那样的说明性语言)支持基于依赖的任务。尝试一下,您就会明白我的意思。



参考资料

学习
  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • Raven: Scripting Java Builds with Ruby (Matthieu Riou,Apress,2007 年):这是由 Raven 的创始人编写的权威的 Raven 指南。
  • Using the Rake Build Language”(Martin Fowler,martinfowler.com,2005 年 8 月):使用 Ruby 构建软件。
  • 脱离 Rails 看 Ruby”(Andrew Glover,developerWorks,2005 年 10 月):在搭上 Rails 潮流之前了解 Ruby。
  • Apache Maven 2 简介”(Sing Li,developerWorks,2006 年 12 月):通过这篇教程可以完成 Maven 2 入门。
  • 实战 Groovy: 用 Groovy 进行 Ant 脚本编程”(Andrew Glover,developerWorks,2004 年 12 月):Andrew Glover 介绍了 Groovy 的 builder 实用程序,该实用程序使得 Groovy 与 Ant、Maven 更容易结合起来,以提供更具表达性、可控制的结构。
  • What's after Ant?”(Andrew Glover,thediscoblog.com,2007 年 4 月):Andrew Glover 在其中谈到构建语言的未来发展趋势。
  • 让开发自动化 (Paul Duvall,developerWorks):阅读整个系列。
  • developerWorks Java 技术专区:这里有数百篇关于 Java 编程的文章。

获得产品和技术
  • Raven:下载 Raven 和 JRuby。
  • Ruby:下载 Ruby 自动安装程序。
  • RubyGems:下载 RubyGems 包管理系统。
  • 示例代码:本文中的 Raven 脚本。

讨论
  • Improve Your Code Quality 讨论论坛:developerWorks 文章作家 Andrew Glover 将其丰富的专家知识带入这个论坛,主要在提高代码质量方面提供咨询。
  • Accelerate development space:developerWorks 文章作者 Andrew Glover 在此开设了一个一站式的门户,其中包括与开发人员测试、持续集成(Continuous Integration)、代码度量(code metrics) 和重构相关的各种内容。


关于作者

Paul Duvall

Paul Duvall 是 Stelligent Incorporated 的 CTO,该公司是一家咨询公司,在帮助开发团队优化 Agile 软件产品方面被认为是同行中的翘楚。他是 Addison-Wesley Signature 系列书籍 Continuous Integration: Improving Software Quality and Reducing Risk (Addison-Wesley,2007 年)的作者之一。他对 UML 2 Toolkit (Wiley,2003 年)和 No Fluff Just Stuff Anthology (Pragmatic Programmers,2007 年)也有贡献。