简介:高效使用Java构建工具|Maven篇。众所周知,当前最主流的Java构建工具为Maven/Gradle/Bazel,针对每一个工具,我将分别从日常工作中常见的场景问题切入,例如依赖管理、构建加速、灵活开发、高效迁移等,针对性地介绍如何高效灵活地用好这3个工具。

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

但是需要注意不恰当的使用并行能力进行测试,反而可能带来副作用。比如当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,欢迎持续关注我们。

原文链接

本文为阿里云原创内容,未经允许不得转载。

高效使用Java构建工具|Maven篇|云效工程师指北的更多相关文章

  1. 高效使用Java构建工具,Maven篇|云效工程师指北

    大家好,我是胡晓宇,目前在云效主要负责Flow流水线编排.任务调度与执行引擎相关的工作. 作为一个有多年Java开发测试工具链开发经验的CRUD专家,使用过所有主流的Java构建工具,对于如何高效使用 ...

  2. 项目管理构建工具——Maven(基础篇)

    项目管理构建工具--Maven(基础篇) 在前面的内容中我们学习了JDBC并且接触到了jar包概念 在后面我们的实际开发中会接触到很多jar包,jar包的导入需要到互联网上进行就会导致操作繁琐 Mav ...

  3. 项目管理构建工具——Maven(高阶篇)

    项目管理构建工具--Maven(高阶篇) 我们在之前的文章中已经基本了解了Maven,但也仅仅只止步于了解 Maven作为我们项目管理构建的常用工具,具备许多功能,在这篇文章中我们来仔细介绍 分模块开 ...

  4. Java项目工程化之项目构建工具Maven

    欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系.本系列教程希望您能站在上帝 ...

  5. 构建工具maven

     构建工具maven  =UTF-8''Gradle Effective Implementation Guide.pdf: http://www.t00y.com/file/76854506 b ...

  6. 项目管理及自动构建工具Maven

    项目管理及自动构建工具Maven 一.Maven安装.目录结构.cmd命令1.下载安装apache-maven-3.2.3-bin.zip下载:http://maven.apache.org/down ...

  7. ③---Java项目管理工具MAVEN安装与配置

    Java项目管理工具MAVEN安装配置以下将为大家介绍Java项目管理工具MAVEN安装及其配置. 一.下载MAVEN安装文件 maven下载地址:https://maven.apache.org/d ...

  8. 着重基础之—构建工具—Maven的依赖管理

    着重基础之—构建工具—Maven的依赖管理 项目构建利器Maven给我们开发人员带来了极大的便利,从繁琐的jar包管理中脱身的程序员终于可以有时间再进入另一个坑了. 我今天要给大家分享的内容是我在实际 ...

  9. Java构建工具:如何用Maven,Gradle和Ant+Ivy进行依赖管理

    原文来自:https://zeroturnaround.com/rebellabs/java-build-tools-how-dependency-management-works-with-mave ...

  10. 项目构建工具maven的使用方法

    最近在开发javaweb项目中有用到maven,以前并不是很了解,于是学习了一些相关内容,记之共享. 本篇内容在Windows环境下实施,JDK版本使用的1.7.0_79. 一.maven是什么? 简 ...

随机推荐

  1. day34-IO流01

    IO流01 1.文件基础知识 什么是文件? 文件,我们并不陌生.文件是保存数据的地方.比如大家经常使用的word文档,txt文件,excel文件等,都是文件.它既可以保存一张图片,也可以保存声音.视频 ...

  2. Vuejs右键弹出菜单

    一.安装 npm install @xunlei/vue-context-menu 二.修改main.js import VueContextMenu from '@xunlei/vue-contex ...

  3. [C++]使用auto遍历判断是否是最后一个元素

    一.背景 略 二.代码 for(auto& it:vec){ if(&it==&vec.back()){ cout<<"is the last eleme ...

  4. C++中虚表是什么

    虚函数表,以及虚函数指针是实现多态性(Polymorphism)的关键机制.多态性允许我们通过基类的指针或引用来调用派生类的函数 定义 虚函数(Virtual Function) 定义:类中使用vir ...

  5. tomcat中虚拟主机以及web应用程序的配置

    一:新建虚拟主机 1. 在tomcat里新建文件夹myapps,在里面添加ROOT文件,放入网站的首页文件 新建文本文档,输入你想要的内容我这里的内容是TOM.AI,把文本文档的名字改成index.h ...

  6. verilog语法基础学习系列

    verilog语法 1.学习目标 verilog语法是数字电路的基础.好像大部分的数字电路工程师都需要在面试时回答相关问题,一些甚至需要对该块的知识进行机考.所以,这部分的知识需要明确的概念和结合数字 ...

  7. Involution:空间不共享?可完全替代卷积的高性能算子 | CVPR 2021

    其实这篇文章很早就写好了,但作者其它论文涉及到洗稿问题,所以先放着了.目前看这篇文章没被举报有洗稿的嫌疑,所以就发出来了 . 来源:晓飞的算法工程笔记 公众号 论文: Involution: Inve ...

  8. KingbaseES V8R6集群运维案例之---自动清理集群主库wal日志

    ​ 案例说明: 在KingbaseES V8R6 主备流复制的集群,配置复制槽(replication slot).复制槽提供了一种自动化的方法来确保主控机在所有的后备机收到 WAL 段 之前不会移除 ...

  9. sql语句TRUNCATE 清空表数据

    清空表数据 TRUNCATE TABLE zzsfp_hwmx;

  10. MySQL数据库维护和改善性能

    备份数据   由于MySQL数据库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据.但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不一定总是有效.下面列出这个问题的可能解决 ...