大家好,我是胡小鱼,目前在云霄负责Flow流水线编排、任务调度和执行引擎相关工作。作为一名拥有多年Java开发和测试工具链开发经验的CRUD专家,使用过所有主流的Java构建工具,积累了一套高效使用Java构建工具的方法。众所周知,最主流的Java构建工具是Maven/Gradle/Bazel。对于每一个工具,我都会从日常工作中常见的场景问题出发,比如依赖管理、构建加速、柔性开发、高效迁移等,有针对性地讲解如何高效灵活地使用这三个工具。Java构建工具的前世今生在古代,Java是使用make构建的,编写Java构建的makefile非常笨拙和不方便。然后ApacheAnt诞生了。Ant可以灵活定义清洗、编译、测试、打包等流程。但是,由于缺少依赖管理功能,需要编写复杂的xml,仍然存在很多不便。然后ApacheMaven诞生了。Maven是一个依赖项管理和构建自动化工具,它遵循约定优于配置的规则。虽然也需要写xml,但是比较容易管理复杂的项目,项目结构标准化,依赖管理清晰。另外,由于Maven本质上是一个插件式的执行框架,所以也提供了一定的开放性。我们可以利用Maven的插件化开发,为构建组合创造一定的灵活性。但是由于采用了约定大于配置的方式,损失了一定的灵活性。同时,由于使用xml来管理构建过程和依赖关系,随着项目的扩大,配置管理还是会带来很多的复杂性。在这样的背景下,诞生了兼具Ant和Maven各自优点的集合Gradle。Gradle也是一个集成了依赖管理和构建自动化的工具。首先,他不再使用XML而是使用基于Groovy的DSL来串联描述任务来串联整个构建过程,同时也支持插件提供类似Maven的基于契约的构建。Gradle除了在构建依赖管理上有很多优势,在构建速度上也有优势,提供强大的缓存和增量构建能力。除了上述Java构建工具外,谷歌在2015年开源了一款功能强大但难度较大的分布式构建工具Bazel,它具有多语言、跨平台、增量构建可靠等特点。它可以在建筑中加倍。提高构建速度,因为它只重新编译需要重新编译的文件。Bazel还提供分布式远程构建和远程构建缓存,以帮助提高构建速度。目前业内使用Ant的人比较少,主要使用Maven、Gradle、Bazel。如何根据这三种工具的特点,真正发挥出它们的效用,是本系列文章要帮助大家解决的问题。让我们从Maven开始吧。优雅高效的使用Maven我们在维护一个Maven项目的时候,关注以下三个问题可以帮助我们更好的使用Maven。●如何优雅地管理依赖●如何加速我们的构建和测试过程●如何扩展自己的插件进行优雅的依赖管理在依赖管理中,有以下实用的原则,可以帮助我们实现不同场景下的依赖管理优雅而高效。●在父模块中使用dependencyManagement配置依赖●在子模块中使用dependencies来使用依赖●使用profiles进行多环境管理以我日常开发中维护的一个标准的spring-boot多模块Maven项目为例。项目中模块之间的依赖关系如下,通常是一个标准的spring-bootrestfulapi多模块项目的结构。便捷的依赖升级通常我们在升级依赖的时候会遇到如下问题:日志输出所需的logback依赖,可以看出我们遵循了以下原则:(1)在所有子模块的父模块的pom中配置依赖管理,统一管理依赖版本。直接在子模块中配置依赖,不再需要纠结具体版本,避免潜在的依赖版本冲突。(2)将相同groupId的依赖配置在一起。比如groupId是org.springframework.boot,我们一起配置。(3)groupId相同,但是需要将一组依赖公共功能的artifactId配置在一起,同时将版本号提取到一个变量中,方便后续一组的版本升级的共同功能。比如将spring-boot依赖的版本提取到spring-boot.version中。在子模块build-engine-api的pom.xml中,由于父pom中配置了依赖dependencyManagement的spring-boot相关依赖的版本,所以在子模块的pom中,只需要声明依赖即可直接在依赖项中。保证了依赖版本的一致性。合理的依赖范围Maven依赖范围的定义,compile/provied/runtime/test/system/import,原则上只根据实际情况配置依赖范围,只在必要的阶段引入必要的依赖.90%的Java程序员应该使用过org.projectlombok:lombok来简化我们的代码。原理是在编译时将注解转化为Java实现。因此,提供了这个依赖的范围,即在编译时需要,但在构建最终产品时需要排除。当您的代码需要使用jdbc连接到mysql数据库时,通常我们希望针对标准JDBC抽象进行编码,而不是直接错误地使用MySQL驱动程序实现。这时需要将依赖作用域设置为runtime。这意味着我们不能在编译时使用这个依赖,这个依赖会被包含在最终的产品中,在程序最终执行的时候在类路径下才能找到。在子模块dao中,我们有测试sql的场景,需要引入内存数据库h2。因此,我们将h2的作用域设置为test,以便我们在测试编译和执行时可以使用它,同时避免它出现在最终产品中。更多scope的使用请参考官方帮助文档。多环境支持举个简单的例子,当我们的服务部署在公有云上时,我们在云上使用一个MySQL8.0版本,当我们要部署在私有云上时,用户提供一个自运维的版本MySQL5.7。因此,我们在不同的环境下使用不同版本的mysql:mysql-connector-java。同样,在项目的实际开发过程中,我们经常会面对同一套代码。部署在多个环境中,存在一些依赖不一致的情况。profiles的更多用法可以参考官方帮助文档依赖纠错如果你在父pom中使用dependencyManagement锁定依赖版本,大概率很少会遇到依赖冲突。但是当你还是无意中看到NoSuchMethodError和ClassNotFoundException时,有两种方法可以快速为你纠正错误。(1)通过依赖分析找到冲突的依赖(2)通过添加stdout代码找出冲突的类实际上是在寻找哪些依赖,通过特定路径下对应的版本信息找到对应的版本并修正。当然,该方法也可以纠正一些错误加载到类路径中的依赖,以及非项目依赖配置导致的冲突。加速测试构建过程作为开发人员,我们总是希望自己的项目无论在什么情况下都能快速、稳定地执行,所以在使用Maven的过程中,需要遵循以下原则。●尽可能复用缓存●尽可能并行构建或测试依赖于下载加速通常情况下,根据Maven配置文件${user.home}/.m2/settings.xml中的配置,默认是缓存在${user.home}/.m2/settings.xmluser.home}/.m2/repository/中。通常在构建过程中,依赖的下载往往会成为一个耗时的环节,但是通过一些简单的设置,我们可以有效的减少依赖的下载和更新。●优化updatePolicy设置updatePolicy指定尝试更新的频率。Maven将本地POM的时间戳(存储在存储库的maven-metadata文件中)与远程进行比较。选项有:always(总是)、daily(每天,默认)、interval:X(其中X是一个以分钟为单位的整数)、never(从不)。●使用离线构建另外,如果构建环境中已经有缓存,可以使用Maven的离线模式构建,避免下载更新依赖或插件。直观上,日志中不会出现以下Downloading相关的信息。构建过程加速默认情况下,Maven构建过程不会完全使用硬件的全部功能,它会顺序构建Maven项目的每个模块。这时候如果可以使用并行构建,就有机会提升构建速度。以上是并行内置的两条命令,可以根据实际cpu情况选择对应的命令。但是如果你发现构建时间并没有减少,那么可能你的maven模块之间存在类似的依赖关系,模块之间只是简单的pass。那么并行构建不适用于你。如果你的模块之间存在并行依赖的可能性,那么使用上面的命令来构建以使并行构建工作。测试过程的加速当我们尝试对maven项目的测试用例部分进行加速时,不得不提到一个插件,maven-surefire-plugin。当您执行mvntest时,surefire插件默认工作。如果我们想在测试中使用并行能力,我们可以配置如下。
