最近用 maven 有些槽点(当然也可能是我自己对 maven 的学习也不够深入), 我既写 java 也写 scala, 有人在 scala 帖子诟病的 sbt 难用,相比之下 maven 才是真的“难用”。
我说的难用不是指难安装、简单打包,而是带一些场景:
xml 可读性某些层面太差了,过于繁琐,对版本管理,子模块管理全部揉杂在一起
我们的项目组开发是一个 framework 的,也就是自身需要维护一大堆依赖,被别人引用的时候也会带上这些依赖的版本。
我们尝试过像 spring 那样用:
project
:只作为根入口dependencies
:管理所有依赖,包括自身子模块的依赖,相当于 BOMparent
:其他子模块的父项目,用于预定义一些构建( spotless 、spotbugs 等)但是 IDEA 的识别不够好,总是出现某些子模块的版本找不到( IDEA 无法识别到这些子模块就是我维护的,而不是第三方库,而是反过来,总是去远仓库下载而不是项目的 target 上查找)
后面我们直接用 root 管理所有的 dependenices ,还是有问题,但是遇到问题只能本地 install 一下
我们用的是 Gitlab CI/CD ,由于模块太多,一个 mvn test 运行太慢了,等个 CI 的功夫能干很多事情,但是我们尽可能希望快一点验证 commit ,继续做后续的测试,所以我们搞了按模块区分的单测:例如 coreTest 、serverTest 、extensionTest 等。。。
maven 可以用 mvn test -pl core -pl server
这样解决,但是部分 extension 模块是依赖 server 或者 core 的,就只能用 mvn test -pl extension -am
also make 依赖,这样的后果就是跑 extension 的时候,把 core 的单测也跑了(这还加速啥,依赖多的项目,很大概率直接跑个完整的 test )。 这个问题,我们后面用 profiles 解决了,但是维护起来太鸡巴蛋疼,太繁琐了(重构的时候)
我改造 CICD 的时候,希望能利用上缓存机制,多个 task 来加速,但是我发现 compile 的时候也会把 validate 阶段的 enforce 插件也给跑了... 问题是,我看了下好像没有命令来看执行 xx 的时候会同时执行什么,只能跑一下 xx 然后看。。。
可能我用 maven 的姿势不太对,但是越深入就越感觉这玩意不适合复杂项目,就适合简单做个:
clean 、test 、package 、install 、deploy
怀念 sbt...(尝试过 gradle 、迁移看了半天,需要考虑点有点多,还没正式打算改过去,改过去也不知道好不好)
ps:写 Java 的人里可能有大神,但是只写 Java ,写久了真的会降智(不思考合理性,不愿意接受其他,是的,我说的是我自己)
101
iseki 189 天前
|
102
iseki 189 天前
@zhenjiachen Gradle 主要是冷启动比较慢,很大程度上这还是 Kotlin 导致的(因为要编译 kts 脚本和 buildSrc ,Kotlin 编译慢的离谱),后续的话,任务缓存和 daemon 等等机制理论上是非常高效的
|
103
31415926535x 189 天前
问个问题,为什么这种多个模块的项目都要放到同一个仓库中并且由一个 idea 打开呢,maven 确实一般都是用在层级比较小的项目的构建,那为什么不拆分到多个仓库中,分开并行构建(或者用 git submodule ),因为每次改动不是所有的模块都是会修改的吧,而且即使有应该也是一步步来开发测试的
然后为什么 ut 会依赖其他模块的类呢,ut 不是一般都是测当前类的方法么,,如果是集成测试,是不是还是在一个统一的维护 case 的平台来操作呢,pipeline 的单测通过之后,触发集成测试的验证 |
104
iseki 189 天前 via Android
@31415926535x 多个版本库之间管理是额外的成本,git submodule 并不好用,特别是模块间关系紧密,连版本号都完全统一时。
|
105
blueswhisper 188 天前
@diagnostics 不至于。只是做框架的研发,对构建工具使用认知在这种水平,让我质疑写的框架代码的质量。
|
106
diagnostics OP @31415926535x 你能这么问,说明你没维护过结构复杂点的项目呗
以 spring 来说, - aop 依赖 core 、bean - bean 依赖 core - context 依赖 aop 、bean 、core 你想单独跑 aop 的单测是不行的,一定会先去编译相应的依赖( core 、bean )两个方案: 1. 在单测前编译一轮,缓存起来 2. 用 maven 参数 --also-make 编译依赖 如果你用 2 ,不在 ci 搞文章,或者你本地想测试了,直接 mvn clean test -pl aop -am 的话,会把 core 、bean 的单测一起执行了,这是因为 maven 对依赖只有一个 artifact id 声明,没有其他依赖关系,以在 V2EX 被人吐槽的 sbt 为例,依赖可以是 .dependsOn(bean % "compile->compile;test->test", core) 上面的含义是,依赖 bean 的代码和测试,依赖 core 的代码。 maven 能做到吗?也可以:mvn clean compile -pl aop -am & mvn test -pl aop 我一直吐槽的是,maven 做起来复杂,而不是 maven 无法实现,但是楼里有些人冷嘲热讽,说白了这些人不愿意接受新鲜东西,老古董,我发现文化人大多都有这个问题,学多了就认为自己的体系是对的。 |
107
yusheng88 188 天前
竟然这么多人评论了, 我也不吐不快一下吧。
1 、你不是来讨论问题的,是来评判 maven 不好的 你用过其它语言跟构建工具,与你遇到 maven 问题有什么关系? 正常提问,应该是如何解决 maven 的 xx 问题吧 2 、你的标题跟内容有一毛钱关系吗? 别人进来一看,通篇都是吐槽 maven 不行,其它语言、构建工具多好啥,哪里会关注你真正问题 用个语言会降智,看你的描述确实是。 自己用起来不顺手,无法解决的就是复杂项目,也是搞笑。 3 、maven 问题描述不清晰 我之前待过基础框架开发组,没遇到过你说的问题 你的问题描述就跟实习生说的一样:我怎么连接数据库失败? 建议你直接在 github 弄个 demo ,你的描述问题能力太差了。 |
108
diagnostics OP |
109
diagnostics OP @yusheng88 你说的没错,我确实是来骂 maven 的,因为我遇到的问题,基本上都在 maven 不好解决,设计问题,我觉得 maven 太老了,吐槽一下。就算能解决我说的问题,也是很复杂的解决,这算一个好框架?
基础框架组遇到过,但是没遇到过这问题,emmmmm 其实我说的问题,不搞 cicd 并行化,也比较难遇到,只能说人和人的追求不一样,有些人写代码只是能跑就行,和基础框架组没什么关系。有些人会去思考,怎么跑得更好,怎么设计的更好。 我的同事,非组内的,学习积极吗?积极,各种书籍天天看,实际在工作里,代码能写多捞就写多捞。 |
110
diagnostics OP @blueswhisper https://github.com/d789a08a-66a8298fd305/maven-issue
“Talk is cheap. Show me the code.” 问题直接贴到 Github 了,欢迎您提 PR 给出一个 Maven 下优雅的解决方案 |
111
31415926535x 188 天前
@iseki 但是现在的问题不就是模块太多且在相互依赖时影响效率么,那应该拆分才对的,如果非常紧密是应该在一起并且统一管理的
submodule 好像确实是有很多坑,,不过应该可以用这种思想来开发,主要还是得维护好多个模块在 push 之后的触发的 ci 逻辑就行 |
112
31415926535x 188 天前
@diagnostics
为什么假定结构复杂的项目就是所谓的中间件、某某框架呢? 我的讨论点是既然一开始就提到了开发编译测试构建这个流程效率很低了,且明知 mvn 工具的羸弱,为什么不审视自己的项目是否结构合理而进行重构,以选择更好的工具链来加速流程(如果你是为了来讨论 出 一个解决这个场景问的话) > 你想单独跑 aop 的单测是不行的,一定会先去编译相应的依赖( core 、bean )两个方案: 假定你说是要只关心 aop 这个模块,那可以认为其他有依赖的都是已经搞好的,那何不分出来多个仓库让 ci 并行编译其他的依赖项,这不是天然的缓存么,而本地只关心 aop 这个模块,重新导入编译好的依赖项不久可以测试 aop 模块了 而且我认为 mvn 的命令应该 i 是用来维护当前项目的(即应该是对根 pom 操作),而不应该从子模块中间来执行 而且都已经是大型项目了,难道从头到尾每次 future 都是一个人维护?一个仓库模式多个人公用一个开发分支来协同开发吗? 工具不好用,不应该立马选择更优的工具链路么。没有选择那不是说明没有到 “工具不好用”的瓶颈吗。这怎么还有 |
113
iseki 188 天前
@31415926535x 模块之间的紧密度,在切分仓库和单一模块之间还有一个层级,这个层级刚好使用 Maven 的多模块和 Gradle 的多 Project 能力处理。每一个“模块”是一个编译单元,有时候编译单元的切分是必须的。
一般来说一个稍微复杂点的可扩展功能就必须切分为至少两个模块,模型与实现,比如一个叫 xxx-api 另一个叫 xxx-core 之类的。这时,两个模块都会被存储到制品仓库(比如 Maven Central ),而扩展模块只需要依赖 xxx-api 即可。 |
114
iseki 188 天前
此外就是考虑到 Kotlin 这种编译比较缓慢的语言,我在实践中倾向于让每一个模块保持很小的体积,同时让依赖树尽量扁平,这样,IDE 更流畅,Gradle 自身的并发执行任务的能力也对我有很大帮助。
|
115
diagnostics OP @31415926535x #112
1. 我没看懂你提的用更好的工具链来加速这个事情,目前我们是 gitlab ci/cd ,即使我做了 cache 了提前 compile 的产物,再 test ,也没法增量编译,你可以去深入 maven 增量编译这个事情。 2. 项目结构合理性:这个我们考虑过,可以用接口把一些依赖耦合给解耦,例如 aop -> bean 的依赖,bean 可以搞个接口模块,这样即使 aop 测试需要一来 bean-interface, 但是也不会跑任何实现的逻辑,bean 可以单独跑。但问题是:不是所有模块都支持这样、以及工程会有点复杂。换句大白话说,这是你构建工具可以解决的事情。 3. 你说的先构建并行吧啦巴拉,这些,我大概理解你的意思。和 1 差不多,maven 不支持增量编译,所以应该需要先打包 deploy 到 nexus ,才能被其他模块拉到并更新。我们要做的事情就是 ci/cd 里面搞并行化。aop 、bean 本质上是同级模块,只不过有依赖关系,强行搞成两个 stage ,会遇到我说的,先 deploy 才行。 4. 是 feature 不是 future ,我感觉你工作经验不久?或者接触的项目比较单一(都是公司里面做单个微服务项目),你随便找个开源项目就知道开源 git 的工作流和公司里面的业务项目不太一样,简单来说分为两个: - Trunk Based Development:基于 dev 、sit 、prd 这种分支开发,每个人提交到 dev ,提交合并需要 review - Github flow/ Gitlab flow:拉出一个分支开发,再合并到 main 分支,根据发版还有不同的 release 这是 Git 用法的两个模式,和本文完全没关系,我没太理解你认为适合的 workflow 是那一种,目前我们是 gitlab flow |
116
diagnostics OP @iseki #114 不知道你用的是不是 maven 、假如不搞 profiles ,那么多项目多插件,绝对让你的编译速度直线下降:
你可以看我帖子里的 github 仓库的分析有解释,maven 没有定义具体能依赖什么阶段,你执行 package 就会把之前的所有阶段一起跑了,包括你依赖的模块 多项目,多插件之后,执行速度是 :模块数量 * 插件数量 对于大型项目,spotless, findbugs, enforce, sortpom 等校验插件是平常的事, 如果内部还有其他定制化和功能插件,那速度更别说了 |
117
iseki 188 天前
@diagnostics 我只有一个迫不得已用 Maven 的项目用了 Maven ,那是因为目标平台用了 OSGi ,相关构建插件在 Gradle 中非常不成熟。我观测的也是,Maven 在构建和配置缓存的利用上非常不优化,我不确定 Maven 平台有没有提供这种能力。
Gradle 的一个大问题在于冷启动实在是太慢了,如果使用了 buildSrc (aka. convention precompiled plugin) 问题就更加严重,偏偏官方鼓励使用这个··· |
118
ikas 187 天前
看了你的 maven 仓库
在 maven3 中,如果不是直接操作 root 项目,那么就需要你指明依赖 在 maven4 中,maven 会自动查找 root 项目 关于无法指定依赖是依赖模块源码,还是仓库中的模块 我在前面说了,我们一般采用平级模块加各种 root 项目的方案,在 root 中控制 modules 否包含指定的 module 来实现源码依赖与否 在 root 项目中 <profiles> <profile> <id>with-1</id> <modules> <module>module1</module> </modules> </profile> <profile> <id>with-2</id> <modules> <module>module2</module> </modules> </profile> </profiles> 那么首先你需要先保证 module1 已经安装到仓库(已本地仓库为例) cd root mvn install -P with-1 如果你只需要执行 module2 的 test cd root mvn test -P with-2 |
119
diagnostics OP @ikas 1. maven4 解决了依赖问题吗?不过我还没听过 maven4 , 我印象最新的版本还是 3.9 ,刚才去看了下好像还是 alpha 版本
2. 你说的那个在我的 PR 了提了,我的问题其实不是怎么解决,你这个方案,本质上还是 maven 没有特性,导致用户需要投机取巧的方法来实现 |
120
abcbuzhiming 186 天前
@diagnostics 我前面说了那么多,看来你没听进去。
maven 一点也不老,作为一个包管理框架,它工作的非常好。 构建不过是它使用插件实现的附加功能。在这方面,肯定不如天生是为构建工具而生的 grade 。 你的复杂度已经到达了 maven 使用插件无法满足你的地步,那就就应该换更强的工具,而不是在原地抱怨一个并不是专门为构建而生的工具 |
121
ikas 186 天前
这怎么是投机取巧?我觉得你根本没有深入了解 maven,还是执着于 maven 就是垃圾
继承与组合是 maven 重要的特性,这里利用组合方式实现.本身就是其特性 |
122
miracleyao 185 天前
为什么要纠结这些?不是哪个用起来顺手用哪个吗?张三觉得 maven 顺手就用了 maven ,李四觉得 ant 顺手就用了 ant ,王五觉得 make 顺手就用了 make ,李雷觉得 gradle 顺手就用了 gradle ,韩梅梅觉得 sbt 顺手就用了 sbt 。不是用了 gradle 就高人一等的,我用 maven 解决了工作上的问题,顺便把钱赚了。
|
123
diagnostics OP @ikas #121
> 继承与组合是 maven 重要的特性,这里利用组合方式实现.本身就是其特性 哪里的见解? maven composition 包下就一个依赖导入: https://maven.apache.org/ref/3.2.3/maven-model-builder/apidocs/org/apache/maven/model/composition/package-summary.html 搜索 composition in maven 第一个结果也是说 maven 用继承而不是组合的: https://melix.github.io/blog/2021/12/composition-in-gradle.html 官方文档也没有: https://maven.apache.org/guides/introduction/introduction-to-profiles.html 如果你的意思开启多个 profile 是组合的话,那当我没说。 另外 profile 也不是为了开关部分模块而设计的,更多的是环境、用例的不同。例如拿来写不同 JDK 下的编译,就没啥问题,开关 Spotless 好像也可以,但是维护多了有点繁杂 只开关部分模块的,依赖又会出问题,而且你单独开关部分的单测,维护起来一点也不友好. 在多项目里,你需要在每个项目里加这么一坨 shi: <profiles> <profile> <id>xxxTest</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>false</skipTests> </configuration> </plugin> </plugins> </build> </profile> </profiles> 一两个还好,十多个需要维护的模块呢? 为什么在你眼里非黑就是白呢?我觉得 Maven 不适合有复杂构建需求的项目,我说错了吗?你们觉得自己的项目复杂,不代表构建需求复杂。 Maven 能做到一些需求,不代表他做的好;我觉得在我的需求下,maven 做起来很别扭,没问题吧? 假如我能这么写: ```xml <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>example-library</artifactId> <scope>compile-only</scope> // 这里只会引入编译依赖关系,测试相关的任务不会尝试依赖关系 </dependency> ``` 是不是方便许多,我还不需要用 Profiles 维护一大坨 shi 呢?为什么你们喜欢吃咸豆腐,要强迫我也认为咸豆腐是最好吃的? 我抛出我的问题,质疑 Maven 不思进取,大家的态度都是:我用的都好好的,你用不来是你的问题。 Diss 我的几个人,在我发代码之后,写了详细的步骤之后,有人 PR 解决了吗?好几天了,都没消息,这说明啥呢? talk is cheap..... |
124
diagnostics OP @miracleyao 唉,迁移到 Gradle ,又要开始重新学 API ( Gradle 天天搞一些新特性),迁移成本对我们略高(我们现在大概 20 多个子模块。又恨 Maven 不成钢,不知道改进
|
125
diagnostics OP @ikas #121 另外你写的 module 方案,你觉的很优雅的解决了这个问题吗?
maven install -Pwith-1 ,多个项目下,我是不是每个项目都写一个 Profile ?精细度控制? 直接 maven install 先安装全部模块到本地仓库,十几个模块,编译速度还不如我直接 mvn test 的速度快呢,还只跑几个有依赖的模块编译 不过对于 cicd 来说,install 先安装,然后可以把本地 repo 当成共享的 cache ,可以一定程度避免代码的编译 |
126
duanluan 182 天前
感觉都是好复杂好高级的操作,让我想起了以前一家公司 build.gradle 自定义脚本写老长,后来全被我慢慢删了也能正常跑
|
127
ychost 168 天前
@Ayanokouji #1 gradle 老是会出现几个月前的项目编译不过,而 maven 不会,但 gradle 的脚本是真的爽
|