当前位置: 首页 > 后端技术 > Java

Java构建工具高效使用|Maven|云霄工程师指南

时间:2023-04-02 00:19:30 Java

大家好,我是胡小鱼,目前在云霄负责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插件默认工作。如果我们想在测试中使用并行能力,我们可以配置如下。

但需要注意的是不恰当的使用并行能力进行测试,可能会产生副作用。例如,当并行配置为方法时,但由于某种原因测试用例的执行之间有顺序要求,反而会因为用例方法的并行执行导致测试用例失败,所以这也迫使我们,如果我们想要获得更快的测试速度,案例的编写也需要独立高效。更多surefire插件的使用请参考本文档。Maven插件开发Maven本质上是一个插件执行框架,所有的执行过程都是由各个插件独立完成的。maven的核心插件可以参考这个文档。除了maven默认提供的插件,比如maven-install-plugin/mvn-surefire-plugin/mvn-deploy-plugin,还有一些第三方提供的插件,单测覆盖插件mvn-jacoco-plugin,生成api文件swagger-maven-plugin等。在日常工作过程中遇到这样一个问题:一个有明显问题的sql发布到预发布环境。在线的。除了通过必要的codereview访问来避免类似问题,更简单的是,我们可以自己在代码中实现一个SQL扫描的插件,让代码在CI时直接失败,自动避免此类问题。于是我们开发了一个maven插件,使用效果如下:将我们开发部署的插件com.aliyun.yunxiao:mybatis-sql-scan引入到项目中。执行以下命令,或其他包括执行验证阶段的命令。我们会在日志中看到如下插件执行信息。当扫描到缺陷时,构建失败,日志中会出现相应的信息:在GlobalLockMapper.java文件中,我们有一条全表扫描SQL语句,可能存在风险,同时构建失败。下面我将从如何开发异常sql扫描的maven插件开始,帮助大家了解插件开发的流程。1.创建项目生成的示例项目如下,其中MyMojo.java定义了插件的入口实现,在根pom.xml中可以看到,●打包为“maven-plugin”。●在依赖配置上,依赖了一些插件开发的基础二方库。●插件节点下,依赖maven-plugin-plugin帮助我们完成插件的构建。2.Mojo实现在开始实现我们的Mojo之前,我们需要做如下分析:●插件在maven中执行了哪些生命周期●插件执行需要哪些入口参数●执行完成后如何退出插件执行因为我们要实现的插件是做@Update/@Select等mybatis注解扫描,判断是否有异常sql,比如是否有全表扫描的sql,是否有sql对于全表更新等。对于这个场景,由于需要扫描具体的源码,所以需要知道项目源码所在的目录,扫描哪些文件●插件扫描异常时,只是报错,不产生任何报告●希望后面执行mvnvalidate时触发扫描。预期的插件是这样的,那么,●@Mojo(name="check")定义目标●@Parameter○@Parameter(defaultValue="${project}",readonly=true)参数绑定到项目的根目录,project.getCompileSourceRoots()可以获取源码的根路径○我们定义了mapperFiles,用来扫描哪些文件的通配符,excludeFiles用来负责排除哪些文件。execute()○有了上面的基础,我们就可以在execute方法中实现对应的逻辑,当扫描到异常sql时,抛出MojoFailureException,插件就会失败终止。以上,我们就完成了一个插件的基本能力的开发。3.插件打包上传插件开发完成后,我们可以配置distributionManagement,然后执行mvndeploy,完成插件的构建和发布。希望通过我的介绍,能够帮助大家更好的使用maven。在下一篇文章中,我们将讨论Gradle。欢迎继续关注我们。点击下方链接,免费体验云效管道流。https://www.aliyun.com/product/yunxiao/flow?channel=yy_practice