Java 构建工具对比

Java 构建工具对比

在 Linux C 开发中我们常常使用 Make 进行构建,不过 Make 将自己和操作系统绑定在一起了;也就是说,使用 Make,就不能实现(至少很难)跨平台的构建,这对于 Java 来说是非常不友好的。此外,Makefile 的语法也成问题,很多人抱怨 Make 构建失败的原因往往是一个难以发现的空格或 Tab 使用错误。而在 Java 发展过程中常见的自动化构建工具以 Ant、Maven、Gradle 为代表,整个自动化流程往往包含以下步骤:编译源代码、运行单元测试和集成测试、执行静态代码分析、生成分析报告、创建发布版本、部署到目标环境、部署传递过程以及执行冒烟测试和自动功能测试。

和 Make 一样,Ant 也都是过程式的,开发者显式地指定每一个目标,以及完成该目标所需要执行的任务。针对每一个项目,开发者都需要重新编写这一过程,这里其实隐含着很大的重复。Maven 是声明式的,项目构建过程和过程各个阶段所需的工作都由插件实现,并且大部分插件都是现成的,开发者只需要声明项目的基本元素,Maven 就执行内置的、完整的构建过程。这在很大程度上消除了重复。

此外,Ant 是没有依赖管理的,所以很长一段时间 Ant 用户都不得不手工管理依赖,这是一个令人头疼的问题。幸运的是,Ant 用户现在可以借助 Ivy 管理依赖。而对于 Maven 用户来说,依赖管理是理所当然的,Maven 不仅内置了依赖管理,更有一个可能拥有全世界最多 Java 开源软件包的中央仓库,Maven 用户无须进行任何配置就可以直接享用。

而 Gradle 抛弃了 Maven 的基于 XML 的繁琐配置;众所周知 XML 的阅读体验比较差,对于机器来说虽然容易识别,但毕竟是由人去维护的。取而代之的是 Gradle 采用了领域特定语言 Groovy 的配置,大大简化了构建代码的行数。Maven 的设计核心 Convention Over Configuration 被 Gradle 更加发扬光大,而 Gradle 的配置即代码又超越了 Maven。在 Gradle 中任何配置都可以作为代码被执行的,我们也可以随时使用已有的 Ant 脚本(Ant task 是 Gradle 中的一等公民)、Java 类库、Groovy 类库来辅助完成构建任务的编写。

Ant with Ivy

Ant 是第一个“现代”构建工具,在很多方面它有些像 Make。2000 年发布,在很短时间内成为 Java 项目上最流行的构建工具。它的学习曲线很缓,因此不需要什么特殊的准备就能上手。它基于过程式编程的 idea。在最初的版本之后,逐渐具备了支持插件的功能。主要的不足是用 XML 作为脚本编写格式。XML,本质上是层次化的,并不能很好地贴合 Ant 过程化编程的初衷。Ant 的另外一个问题是,除非是很小的项目,否则它的 XML 文件很快就大得无法管理。后来,随着通过网络进行依赖管理成为必备功能,Ant 采用了 Apache Ivy。

Ant 的主要优点在于对构建过程的控制上。Ivy 的依赖需要在 ivy.xml 中指定。我们的例子很简单,只需要依赖 JUnit 和 Hamcrest。

<ivy-module version="2.0">
    <info organisation="org.apache" module="java-build-tools"/>
    <dependencies>
        <dependency org="junit" name="junit" rev="4.11"/>
        <dependency org="org.hamcrest" name="hamcrest-all" rev="1.3"/>
    </dependencies>
</ivy-module>Copy to clipboardErrorCopied

现在我们来创建 Ant 脚本,任务只是编译一个 Jar 文件。最终文件是下面的 build.xml。

<project xmlns:ivy="antlib:org.apache.ivy.ant" name="java-build-tools" default="jar">
    <property name="src.dir" value="src"/>
    <property name="build.dir" value="build"/>
    <property name="classes.dir" value="${build.dir}/classes"/>
    <property name="jar.dir" value="${build.dir}/jar"/>
    <property name="lib.dir" value="lib" />
    <path id="lib.path.id">
        <fileset dir="${lib.dir}" />
    </path>
    <target name="resolve">
        <ivy:retrieve />
    </target>
    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>
    <target name="compile" depends="resolve">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="lib.path.id"/>
    </target>
    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}"/>
    </target>
</project>Copy to clipboardErrorCopied

首先,我们设置了几个属性,然后是一个接一个的 task。我们用 Ivy 来处理依赖,清理,编译和打包,这是几乎所有的 Java 项目都会进行的 task,配置有很多。

运行 Ant task 来生成 Jar 文件,执行下面的命令。

ant jarCopy to clipboardErrorCopied

Maven

Maven 发布于 2004 年。目的是解决码农使用 Ant 所带来的一些问题。Maven 仍旧使用 XML 作为编写构建配置的文件格式,但是,文件结构却有巨大的变化。Ant 需要码农将执行 task 所需的全部命令都一一列出,然而 Maven 依靠约定(convention)并提供现成的可调用的目标(goal)。不仅如此,有可能最重要的一个补充是,Maven 具备从网络上自动下载依赖的能力(Ant 后来通过 Ivy 也具备了这个功能),这一点革命性地改变了我们开发软件的方式。

但是,Maven 也有它的问题。依赖管理不能很好地处理相同库文件不同版本之间的冲突(Ivy 在这方面更好一些)。XML 作为配置文件的格式有严格的结构层次和标准,定制化目标(goal)很困难。因为 Maven 主要聚焦于依赖管理,实际上用 Maven 很难写出复杂、定制化的构建脚本,甚至不如 Ant。用 XML 写的配置文件会变得越来越大,越来越笨重。在大型项目中,它经常什么“特别的”事还没干就有几百行代码。

Maven 的主要优点是生命周期。只要项目基于一定之规,它的整个生命周期都能够轻松搞定,代价是牺牲了灵活性。在对 DSL(Domain Specific Languages)的热情持续高涨之时,通常的想法是设计一套能够解决特定领域问题的语言。在构建这方面,DSL 的一个成功案例就是 Gradle。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.technologyconversations</groupId>
    <artifactId>java-build-tools</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>Copy to clipboardErrorCopied

通过执行下面的命令来运行 Maven goal 生成 Jar 文件。

mvn packageCopy to clipboardErrorCopied

主要的区别在于 Maven 不需要指定执行的操作。我们没有创建 task,而是设置了一些参数(有哪些依赖,用哪些插件...). Maven 推行使用约定并提供了开箱即用的 goals。Ant 和 Maven 的 XML 文件都会随时间而变大,为了说明这一点,我们加入 CheckStyle,FindBugs 和 PMD 插件来进行静态检查,三者是 Java 项目中使用很普遍的的工具。我们希望将所有静态检查的执行以及单元测试一起作为一个单独的 targetVerify。当然我们还应该指定自定义的 checkstyle 配置文件的路径并且确保错误时能够提示。更新后的 Maven 代码如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>2.12.1</version>
    <executions>
        <execution>
            <configuration>
                <configLocation>config/checkstyle/checkstyle.xml</configLocation>
                <consoleOutput>true</consoleOutput>
                <failsOnError>true</failsOnError>
            </configuration>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>findbugs-maven-plugin</artifactId>
    <version>2.5.4</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-pmd-plugin</artifactId>
    <version>3.1</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>Copy to clipboardErrorCopied

通过执行下面的命令来运行 Maven goal,包括单元测试,静态检查,如 CheckStyle,FindBugs 和 PMD。

mvn verifyCopy to clipboardErrorCopied

Gradle

Gradle 结合了前两者的优点,在此基础之上做了很多改进。它具有 Ant 的强大和灵活,又有 Maven 的生命周期管理且易于使用。最终结果就是一个工具在 2012 年华丽诞生并且很快地获得了广泛关注。例如,Google 采用 Gradle 作为 Android OS 的默认构建工具。Gradle 不用 XML,它使用基于 Groovy 的专门的 DSL,从而使 Gradle 构建脚本变得比用 Ant 和 Maven 写的要简洁清晰。Gradle 样板文件的代码很少,这是因为它的 DSL 被设计用于解决特定的问题:贯穿软件的生命周期,从编译,到静态检查,到测试,直到打包和部署。

它使用 Apache Ivy 来处理 Jar 包的依赖。Gradle 的成就可以概括为:约定好,灵活性也高。

apply plugin: 'java'
apply plugin: 'checkstyle'
apply plugin: 'findbugs'
apply plugin: 'pmd'
version = '1.0'
repositories {
    mavenCentral()
}
dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
}
下一节:Maven 是功能强大的构建工具能够帮我们自动化构建过程,从清理、编译、测试到生成报告,再到打包和部署。我们只需要输入简单的命令(如 mvn clean install),Maven 就会帮我们处理繁琐的任务;它最大化的消除了构建的重复,抽象了构建生命周期,并且为绝大部分的构建任务提供了已实现的插件。比如说测试,我们只需要遵循 Maven 的约定编写好测试用例,当我们运行构建的时候,这些测试便会自动运行。除此之外,Maven 能帮助我们标准化构建过程。在 Maven 之前,十个项目可能有十种构建方式,但通过 Maven,所有项目的构建命令都是简单一致的。有利于促进项目团队的标准化。