大家好,我是胡晓宇,目前在云效次要负责Flow流水线编排、任务调度与执行引擎相干的工作。
作为一个有多年Java开发测试工具链开发教训的CRUD专家,应用过所有支流的Java构建工具,对于如何高效应用Java构建工具积淀了一套办法。家喻户晓,以后最支流的Java构建工具为Maven/Gradle/Bazel,针对每一个工具,我将别离从日常工作中常见的场景问题切入,例如依赖治理、构建减速、灵便开发、高效迁徙等,针对性地介绍如何高效灵便地用好这3个工具。
Java构建工具的前世今生
在上古时代,Java的构建都在应用make,编写makefile来进行Java构建有十分多顺当与不便的中央。
紧接着Apache Ant诞生了,Ant能够灵便的定义清理编译测试打包等过程,然而因为没有依赖治理的性能,以及须要编写简单的xml,还是存在着诸多的不便。
随后Apache Maven诞生了,Maven是一个依赖项治理和构建自动化工具,遵循着约定大于配置的规定。尽管也须要编写xml,然而对于简单工程更加容易治理,有着标准化的工程构造,清晰的依赖治理。此外,因为Maven实质上是一个插件执行框架,也提供了肯定的开放性的能力,咱们能够通过Maven的插件开发,为构建形成发明肯定的灵活性。
然而因为采纳约定大于配置的形式,丢失了肯定的灵活性,同时因为采纳xml治理构建过程与依赖,随着工程的收缩,配置管理还是会带来不小的复杂度,在这个背景下,汇合了Ant与Maven各自劣势的Gradle诞生了。
Gradle也是一个汇合了依赖治理与构建自动化的工具。首要的他不再应用XML而是基于Groovy的DSL来形容工作串联起整个构建过程,同时也反对插件提供相似于Maven基于约定的构建。除了在构建依赖治理上的诸多劣势之外,Gradle在构建速度上也更具劣势,提供了弱小的缓存与增量构建的能力。
除了以上Java构建工具之外,Google在2015年开源了一款弱小,但上手难度较大的分布式构建工具Bazel,具备多语言、跨平台、牢靠增量构建的特点,在构建上能够成倍进步构建速度,因为它只从新编译须要从新编译的文件。Bazel也提供了分布式近程构建和近程构建缓存两种形式来帮忙晋升构建速度。
目前业内应用Ant的人曾经比拟少,次要都在用Maven、Gradle和Bazel,如何真正基于这三款工具的特点施展出他们最大的效用,是这个系列文章要帮大家解决的问题。先从Maven说起。
优雅高效地用好Maven
当咱们正在保护一个Maven工程时,关注以下三个问题,能够帮忙咱们更好的应用Maven。
● 如何优雅的治理依赖
● 如何减速咱们的构建测试过程
● 如何扩大咱们本人的插件
优雅的依赖治理
在依赖治理中,有以下几个实际准则,能够帮忙咱们优雅高效的实现不同场景下的依赖治理。
● 在父模块中应用dependencyManagement,配置依赖
● 在子模块中应用dependencies,应用依赖
● 应用profiles,进行多环境治理
以我在日常开发中保护的一个规范的spring-boot多模块Maven工程为例。
工程内各个module之间的依赖关系如下,通常这也是规范的 spring-boot restful api多模块工程的构造。
便捷的依赖降级
通常咱们在依赖降级的时候会遇到以下问题:
● 多个依赖关联降级
● 多个模块须要一起降级
在父模块的pom.xml中,咱们配置了根底的spring-boot依赖,也配置了日志输入须要的logback依赖,能够看出,咱们遵循了以下的准则:
(1)在所有子模块的父模块中的pom中配置dependencyManagement,对立治理依赖版本。在子模块中间接配置依赖,不必再纠缠于具体的版本,防止潜在的依赖版本抵触。
(2)把groupId雷同的依赖,配置在一起,比方groupId为org.springframework.boot,咱们配置在了一起。
(3)把groupId雷同,然而须要一组依赖独特提供性能的artifactId,配置在一起,同时将版本号抽取成变量,便于后续一组性能独特的版本升级。比方spring-boot依赖的版本抽取成了spring-boot.version。
在子模块build-engine-api的pom.xml中,因为在父pom中配置了 dependencyManagement中依赖的spring-boot相干依赖的版本,因而在子模块的pom中,只须要在dependencies中间接申明依赖,确保了依赖版本的一致性。
正当的依赖范畴
Maven依赖有依赖范畴(scope)的定义,compile/provieded/runtime/test/system/import,原则上,只依照理论状况配置依赖的范畴,在必要的阶段,只引入必要的依赖。
90%的Java程序员应该都应用过org.projectlombok:lombok来简化咱们的代码,其原理就是在编译过程中将注解转化为Java实现。因而该依赖的scope为provided,也就是编译时须要,但在构建出最终产物时又须要被排除。
当你的代码须要应用jdbc连贯一个mysql数据库,通常咱们会心愿针对规范 JDBC 形象进行编码,而不是间接谬误的应用 MySQL driver实现。这个时候依赖的scope就须要设置为runtime。这意味着咱们在编译时无奈应用该依赖,该依赖会被蕴含在最终的产物中,在程序最终执行时能够在classpath下找到它。
在子模块dao中,咱们有对sql进行测试的场景,须要引入内存数据库h2。
因而,咱们将h2的scope设置为test,这样咱们在测试编译和执行时能够应用,同时防止其呈现在最终的产物中。
更多对于scope的应用,能够参考官网帮忙文档。
多环境反对
举个简略的例子,当咱们的服务在私有云部署时,咱们应用了一个云上版本为8.0的MySQL,而当咱们要进行专有云部署时,用户提供一个自运维的版本为5.7的MySQL。因而,咱们在不同的环境中应用不同的 mysql:mysql-connector-java 版本。
相似的,在我的项目理论的开发过程中,咱们常常会面临同一套代码。在多套环境中部署,存在局部依赖不统一的状况。
对于profiles的更多用法,能够参考官网帮忙文档
依赖纠错
如果你曾经在父pom中应用dependencyManagement来锁定依赖版本,大概率的,你简直很少会碰到依赖抵触的状况。
然而当你还是意外的看到了NoSuchMethodError,ClassNotFoundException 这两个异样的时候,有以下两个办法能够疾速的帮你纠错。
(1)通过依赖剖析找到抵触的依赖
(2)通过增加stdout代码找到抵触的类理论是从哪个依赖中查找的
通过具体的门路中对应的版本信息,找到对应的版本并校对。
当然这个办法也能够纠出一些依赖被谬误的加载到classpath下,非工程自身依赖配置引起的抵触。
测试构建过程减速
作为一个开发者,总会心愿咱们的工程无论在什么状况下,执行的又快又稳,那么在Maven的应用过程中,须要遵循以下准则。
● 尽可能复用缓存
● 尽可能的并行构建或测试
依赖下载减速
通常状况下,依据Maven配置文件 ${user.home}/.m2/settings.xml 中的配置,默认状况下是缓存在${user.home}/.m2/repository/。
通常在构建过程中,依赖的下载往往会成为比拟耗时的局部,然而通过一些简略的设置,咱们能够无效的缩小依赖的下载与更新。
● 优化updatePolicy设置
updatePolicy指定了尝试更新的频率。Maven 会将本地 POM 的工夫戳(存储在存储库的 maven-metadata 文件中)与近程进行比拟。选项包含:always(总是)、daily(每天,默认值)、interval:X(其中 X 是以分钟为单位的整数)、never(从不)。
● 应用离线构建
除此之外,如果构建环境曾经存在缓存,能够应用Maven的offline模式进行构建,防止依赖或插件的下载更新。
直观的,日志中将不会呈现相似如下Downloading相干的信息。
构建过程减速
在默认状况下,Maven构建的过程并不会充沛的应用你的硬件的全副能力,他会程序的构建你的maven工程的每一个模块。这个时候,如果能够应用并行构建,那么将有机会晋升构建速度。
以上是并行构建的两个命令,能够依据理论的cpu状况来抉择对应的命令。然而如果你发现构建工夫并没有失去缩小,那么你的maven模块间可能存在相似的依赖,模块之间只是一个简略的传递。
那么并行构建对你来说并不实用,如果你的模块间依赖关系存在并行的可能,那么应用上述命令进行构建,能力使并行构建施展成果。
测试过程减速
当咱们尝试减速maven工程测试用例的局部,那么就不得不提到一个插件,maven-surefire-plugin。
当你在执行mvn test的时候,默认状况下就是surefire插件在工作。如果咱们想在测试中应用并行的能力,能够作如下配置。
<p style="text-align:center"></p>
然而须要留神不失当的应用并行能力进行测试,反而可能带来副作用。比方当parallel配置为methods,然而因为某些起因测试用例的执行之间存在程序要求,反而会呈现因为用例办法并行执行,导致用例失败,因而也倒逼咱们,如果想取得更快的测试速度,case的编写也须要独立且高效。
更多对于surefire插件的应用,能够参考这篇文档。
Maven插件开发
maven实质上是一个插件执行框架,所有的执行过程,都是由一个一个插件独立实现的。对于maven的外围插件能够参考这篇文档。
maven默认为咱们提供的这些插件比方maven-install-plugin/mvn-surefire-plugin/mvn-deploy-plugin外,还有一些三方提供的插件,单测覆盖率插件mvn-jacoco-plugin,生成api文档的swagger-maven-plugin等等。
在日常工作的过程中,我碰到了这样一个问题:有个存在显著问题的sql被公布到了预公布环境,同时因为预发与生产应用的是同一个db实例,因为sql的性能问题,影响了线上。
除了通过必要的code review准入,来防止相似的问题,更简略的,咱们能够本人入手实现一个代码中sql扫描的插件,让代码在CI时间接失败掉,自动化的防止此类问题的产生。于是咱们开发了一个maven插件,应用办法和成果如下:
在工程中引入咱们开发并部署好的插件com.aliyun.yunxiao:mybatis-sql-scan。
执行以下命令,或其余蕴含validate阶段执行的命令。
咱们将会在日志中看到如下插件执行的信息
在扫描出缺点时,build失败,并会在日志中呈现对应的信息:
在GlobalLockMapper.java这个文件中,咱们有一条全表扫描的sql语句可能存在危险,
同时build失败。
接下来我会从如何开发这个异样sql扫描的maven插件动手,帮忙大家理解插件开发的过程。
1、创立工程
生成的sample工程如下,
其中MyMojo.java定义了插件的入口实现,
此外在根pom.xml中能够看到,
● packaging为“maven-plugin”。
● 依赖配置中,依赖了一些插件开发的根底二方库。
● 插件节点下,依赖了maven-plugin-plugin帮助咱们实现插件的构建。
2、Mojo实现
在开始实现咱们的Mojo之前,咱们须要做如下剖析:
● 插件在maven的哪个生命周期执行
● 插件在执行时须要哪些入口参数
● 插件执行实现后怎么退出
因为咱们要实现的插件是要做mybatis annotation扫描比方 @Update/@Select,判断是否有异样的sql,比方是否存在全表扫描的sql,是否存在全表更新的sql等,对于此种场景下,
● 因为须要扫描特定的源码,须要晓得工程源码的所在目录,以及扫描哪些文件
● 插件扫描出异样时,只有报错即可,不必产出任何报告
● 心愿在后续执行mvn validate时触发扫描
那么预期中的插件是这样的,
那么,
● @Mojo(name = "check") 定义了goal
● @Parameter
○ @Parameter(defaultValue = "${project}", readonly = true) 参数绑定了工程的根目录 ,project.getCompileSourceRoots()便能够获取到源代码的根门路
○ 咱们定义了mapperFiles,用来负责扫描哪些文件的通配,excludeFiles用来负责排除哪些文件
● execute()
○ 有了以上的根底,在execute办法中咱们便能够实现对应的逻辑,当扫描结出异样的sql时,抛出MojoFailureException异样,插件便会失败终止。
以上,咱们便实现了一个插件的根本能力的开发。
3、插件的打包与上传
插件开发实现后,咱们能够通过配置distributionManagement,而后执行mvn deploy,实现插件的构建与公布。
心愿通过我的介绍,可能帮忙大家更好的应用maven,下一篇咱们讲Gradle,欢送继续关注咱们。
点击下方链接,即可收费体验云效流水线Flow。
https://www.aliyun.com/product/yunxiao/flow?channel=yy_practice