1.5 Java构建工具
- 在本节,我们会看到两个流行的基于Java的构建工具:Ant 和 Maven。我们会讨论他们的特性,看看实际的样例脚本,并概述每个工具的缺点。我们首先来看最早的工具————Ant。
1.5.1 Apache Ant
- Apache Ant(Another Neat Tool)是一个用Java编写的开源构建工具。其主要目的是在Java项目中为常用任务提供自动化,例如编译源代码、运行单元测试、打包JAR文件和生成Javadoc文档。另外,它还为文件系统和存档操作提供了许多不同的预定义任务。如果任何一个任务不满足需求,那么你就可以用Java写新的任务来扩展构建。
- 虽然Ant的核心是用Java编写的,但是build文件是通过XML表示的,这样就可以在任务运行时环境下使用了。Ant不提供依赖管理器,所以你需要自己管理外部依赖。然而,Ant可以和另一个Apache项目Ivy很好的集成,它是一个完善的独立的依赖管理器。要集成Ant和Ivy需要一些额外的工作,而且要为每个独立的项目手动配置。让我们一起看一个样例构建脚本。
构建脚本术语
- 要理解Ant的构建脚本,你需要先了解一些命名规范。一个构建脚本由三个基本元素组成:一个Project、多个target和可用的task。图1.10展示了每个元素之间的关系。
- 在Ant中,task是一段可执行的代码————例如,创建一个目录或者移动一个文件。在构建脚本中,通过预定义的XML标签名来使用一个task。task的行为可以由其暴露出来的属性配置。下面的代码片段展示了如何在构建脚本中调用javac的task来编译Java代码:
<javac srcdir="src" destdir="dest"/> //源代码目录和目标目录由srcdir和destdir属性配置,源代码放在src目录,class文件放在dest目录
- Ant自身带有许多预定义的task,然而你可以用Java语言编写自己的task以扩展脚本功能。
- target是你想要执行的task的一个集合,可以把她想成一个逻辑组。当在命令行中运行Ant时,提供了你想要运行的target的名字。通过声名target直接的依赖关系,就可以创建一个完整的命令链。下面的代码片段展示了两个依赖的target:
<target name="init"> //名字为init的target使用midri任务创建build文件夹
<mkdir dir="build">
</target>
<target name="compile" depends="init"> //名字为compile的target通过javac的Ant任务编译Java源代码。该target依赖于init,所以如果你在命令行运行,init会先执行
<javac srcdir="src" destdir="dest"/>
</target>
- project 是对于所有Ant项目都必须的容器。它是Ant脚本的顶级元素,包含一个或多个target。在每个构建脚本中,你只能定义一个project。下面的代码片段展示了project和target的关系:
<project name="example-build"> //project 包含了一个或多个target和可选属性,例如name,来描述这个project
<target name="init">
<mkdir dir="build">
</target>
<target name="compile" depends="init">
<javac srcdir="src" destdir="dest"/>
</target>
</project>
- 有了对Ant层级结构的基本理解,让我们来看一个完整场景的构建脚本样例。
构建脚本样例
- 假设你想要写这样一个脚本,用ava编译器去编译src目录下的Java源代码,并将结果放在输出目录bui1d中,Java源代码中有一个类依赖于 Apache Commons Lang类库。将类库中的JAR文件放置在 classpath路径下,这样编译器就知道该类库的存在了。在编译完成之后,你想要组装一个JAR文件。对于每一个工作单元,源代码编译和JAR文件的组装都会被分配到个独立的te中你还需要添加另外两个如去做初始化和对输出目录进行清理图1展示了该Am构建本的结构。
- 让我们深入到业务中,是时候用Anmt构建脚本去实现这个例子了。下面的清单展示了整个项目,以及实现该目标所需要的 target。
- 清单1.1 含有编译源代码和组装jar文件任务的Ant脚本
<project name="my-app" default="dist" basedir=".">
<property name="src" location="src"/>
<property name="build" locatio="build"/>
<property name="dist" location="dist"/>
<property name="version" value="1. 0"/>
<target name="init">
<mkdir dir="build">
</target>
<target name="compile" depends="init" description="compile the source">
<javac srcdir="${src}" destdir="${build}" classpath="lib/commons-lang3-3.1.jar" includeantruntime="false"/>
</target>
<target name="dist" depends="compile" description="generate the distribution">
<mkdir dire"s(dist)"/>
<jar jarfile="${dist}/my-app-${version}.jar" basedir=${build}"/>
</target>
<target name="clean"description="clean up">
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>
- Ant没有对如何定义构建的结构强加任何限制。这样让适应一个现有的项目结构变得简单。例如,在样例脚本中,源代码目录和输出目录是随意选择的。通过修改相关的属性可以非常轻松地改变它们。对于 target的定义也是一样的:对于每个target,哪个逻辑需要被执行,以及它们的执行顺序,你拥有完全灵活的选择性。
缺点
- 尽管有这样的灵活性,但是你也应该注意到一些缺点:
- 使用XML作为构建逻辑的定义语言相比于其他更简明的定义语言,会导致构建脚本过于臃肿和啰唆。
- 复杂的构建逻辑会导致又长又难以维护的构建脚本。当尝试用标记语言去定义类似if- then/if-then-else的逻辑语句时,它完全就成了一种负担
- Ant没有提供任何指导来告诉你如何建设项目。在一个企业级配置中,这常常会导致一个 build文件每一次看上去都不一样。常用功能时常被到处拷贝。目中每一个新的开发人员都需要去理解构建中每一个独立的部分
- 你想要知道在构建中有多少个类被编译或者多少个task被执行。Ant没有暴露任何的API能够让你在运行时获取内存模型中的信息。
- 在没有1vy的情况下,使用Amt很难管理依赖。在通常情况下,你需要将JAR文件提交到版本控制系统中,并且手动管理组织结构。