TBH,我是不太想单独针对某个插件/软件写一个单独的问题合集的。。因为这说明这东西bug很多或者很容易出问题,
最好别用了ww但是发现对Java而言,maven(mvn)现在还是用的最多的包管理工具,gradle是后起之秀(用groovy来管理).目前复杂环境mvn经常报错, 网上各种瞎写. 只好单独记载一下相关问题以提高开发效率. 不过得相信我 —- Maven真的不难懂, 耐心一些就好.
0x00.基本知识
首先,不想为了一个经常出问题且不优雅的工具去看一本Maven实战书籍 .就简单说一下mvn的基本概念,以免后面谈问题解决的时候名词乱蹦:
1. 核心过程
Java项目的生命周期大致是 :开发–>编译–>测试(可选)–>打包–>部署(可选)
- mvn跟大部分仓库机制一样,有本地和远程mvn仓库(第一次从远端(官方仓库)下载,保存在本地.m2文件夹中
~/.m2/repository,后面会优先用本地) - 核心通过
pom.xml来记录管理各种包依赖、父子关系、版本…etc (长了基本没法看,我不喜欢xml) pom.xml中名字有不少,最基本的也就几个dependency,module,parent,裸用就dependency即可- dependency是核心,那么如何唯一确定一个包,就通过它的子元素–称为坐标唯一确定。包括
xxID,version,type.. - mvn最常用的命令无外乎
clean,install,package,test,compile(后续说) - 最常见的包关系就是**”递进依赖”** 和 “重复依赖” (递进:A包依赖B包,B又依赖C包,mvn把这条线都下下来。重复:项目同时依赖了A和B包,它们又同时依赖C包,mvn根据策略只导一个)
到此为止,了解以上6点基本概念足矣使用,其它用到再说。
2. 常见命令对比.
在IDEA自带的Maven插件里, 你可以看到一系列的命令: clean, compile, test, package, install, deploy 等, (还有几个基本不会用就不说), 这里简要说一下他们的区别:
- compile: 这个就是单纯编译, 把java文件变为class文件, 不会做任何其他的事. (基本是必须步骤. 极少单独使用)
- package: 就是最常用的把当前项目打包 (常用)
- 如果pom文件设置了打war包, 命令就等价于
mvn war:war - 如果pom文件设置是jar包, 命令就等价于
mvn jar:jar
- 如果pom文件设置了打war包, 命令就等价于
- install: 这个就是先执行package的操作, 然后把包放入本地的mvn仓库里, 比如本地的
.m2目录中 - deploy: 这个是在install的基础上, 再上传到远端maven仓库, 需要单独配置.
从上面可以看出, 虽然mvn的命令看起来一大堆奇奇怪怪, 实际你顺清楚脉络, 可以说一条命令就包含了所有的流程, 它几乎是完全按照**流水线的方式, 一步步加工的. 所以不用害怕, 这东西你自己理顺一次**, 就再不会混了..
然后以上命令一般默认带mvn test ,也就是会执行测试操作, 所以为了加快速度, 经常测试里会使用mvn -DskipTests . 或是-Dmaven.test.skip=true (推荐第二个, 它是既不编译测试类, 也不执行, 第一个会编译.)
0x01.缺失包
这个是最不能接受的了,mvn直接不去下该下的包,然后你发现IDE自己也不去下,也不告诉你原因。。然后大家的解决办法就是选择去命令行无脑clean再重新让mvn下?还是来探究一下。。
- 先检查包是否是私有包(或版本不是官方的,自己修改过,检查方法参考步骤3)
- 检查本地仓库是否存在 (去.m2/repository看)
- 检查你目前的远程库(默认是官方库)是否有你的包/版本(查询地址)
- 查看mvn配置是否出错
- 刷新…重新导入试试
0x02.包冲突
这可能是很头疼的事之一,因为java项目依赖的包很多,版本号也点的飞起,导致有冲突经常调来调去,一个项目调好了另一个又报错了。。 这里来清晰的理一下思路,避免拆东墙补西墙出现
网友说法:
- 不停地重新clean –>update–>run (类似我上面说的)
- 直接拷贝别人的.m2文件夹(思路虽然粗暴,但是速度快,效果不错)
我的思路:
- IDEA下安装mvn-tree插件,看杂乱的xml文件简直要狗带,然后快速显示id,版本号等标识信息
- 先确认是否项目使用了非官方/内部包,那肯定是会出错的,好比你自己定义了个新函数。。
- 排除了低级错误之后, 一般大点的项目,都会很很多个module, 然后每个模块都有个
pom.xml,那肯定会有大家共同使用了一个包,但是版本不同的情况. 这会导致什么呢? (通过mvn-tree插件查看)- 包依赖的java版本不同,但是你最后运行程序肯定只有一个Java环境. 一般高版本可以兼容低版本.
- 包版本不同,可能导致函数有些调用方法不存在或者改名字了. 那就要看优先使用的是高,还是低版本的jar包.
0x03.打包和发布
首先,我们把Java程序的编译打包过程搞清楚. 不是上来就对着mvn命令去敲,出了问题再去临时查查….
- 我们编写的
xx.java, 要通过javac编译为xx.class字节码,这一步我们称之为编译 , - class文件汇聚在一起,可以打包为一个
xx.jar/war. (如果java文件更新了,那么必须得重新编译)
再看看IDEA默认生成maven项目的结构:
src文件夹:- main : 代码类
- test :对应main结构的各种Test类
- resources(可选): 放配置文件
pom.xml
再看看mvn的命令本质: (如有偏差欢迎指出~)
maven能执行各种操作,包括clean,install,package,assembly 本质(我理解)都是调用的模块,底层估计就是java写的代码插件一样…. 比如打jar包模块,打war包模块, 整合打包模块,清理脚本… 只不过有些模块是内置的. 不需要你单独去导入.
1.打包
1.直接打包当前项目代码
首先需要在pom.xml配置编译模块 (注意模块选择有好几个,各自有侧重–比如专门把依赖一起整合的)
1 | <plugin> |
常用命令是
1 | #跳过测试打包到target/下.包名为pom文件中定义的"artifactID-version" |
执行完成后,在target下加入有一个demo-1.0.jar 的包. 它的内部结构是什么呢?
- com (里面是对应的子文件夹,具体是class文件)
- META-INF (里面有pom.xml文件和自动生成的pom.properties记录了基本的版本K-V)
- xx.properties (都在根目录,对应的是之前maven项目的resources文件夹下内容)
可以看出结构还是很简单的.基本就是把原本项目的最核心的东西抽了出来.
2.打包当前代码+依赖包
这里也有两种方案.. 一是把依赖包丢到比如lib目录下. 这可能只需要从你本地的.m2复制一份(我猜?)
再就是把所有包打成一个大的jar包. 比如demo-1.0-jar-with-dependencies.jar (大小会大很多…)
1 | <plugin> |
执行命令mvn assembly:assembly -DskipTests ,target下就有对应的整合包了, 那这里我们就不免有个疑问,上面单独的package就可以了,为什么这要用assembly呢?后面的冒号是? 而且你会发现assembly命令clean的时候会把整个target文件夹都删了.
这种直接全部依赖全打包的好处在于非常无脑,缺点在于包非常大(动辄100MB+). 可能99%的地方都不需要更改,但是你每次需要花2min的时间去打包. 所以实际使用中,一般会选择一种折中的办法. 打包依赖. 但是只打必要的. (大小<10MB) 如何实现呢?
待补充~~
补充: Spring家族或者groovy的需要单独的模块去打包,这就不单独说了.
0x04.下载问题
本质是wall的原因,同样全挂代理是不科学的做法,优先走国内仓库源(阿里mvn仓库),配置方法参考
!!!Source code does not match the bytecode问题!!!
这个问题近期才遇到, 网上一堆瞎扯的.还误导了我好久, 其实本质这是一个mvn和IDEA相关的问题. 解决这个问题, 得搞清楚为什么会出现这个问题. 首先maven打包的时候其实是有选项的:
- 单纯jar包 (如
janus-0.2.1.jar) –> 对应IDEA的Classes –> 此时不借助反编译工具是无法查看java代码的(里面都是.class文件,反编译后也没有注释.) - 源码包(如
janus-0.2.1-sources.jar) –> 对应IDEA的Sources –>此时可以直接查看java源码,也带有注释(此时jar其实只是把.java文件聚合了一下而已.) - JavaDoc的源码包(如
janus-0.2.1-javadoc.jar) –>对应IDEA的JavaDocs –>里面都是javadoc后的html,还记得JDK7/8-API电子书一样的文档么? 就是这个组成的.. 内容是你的/** */文档注释内容
为什么有这几个选项,因为用途不一样,只是单纯使用当然应该尽量减少字符占用,甚至各种变量命名/空格等不必要字符,就像js/css文件如果只是使用都会尽可能的压缩. (这里主要是讨论1和2)
那为什么会出现标题这样”源码包和class文件对不上”的问题呢? 因为IDEA自动下载的源码版本与本地jar包不一致
IDEA按Ctrl+Alt+Shift+s 可以看到右侧有个Libraries . 点开是整个maven项目的完整依赖. 你可以发现绝大部分的jar包都是只有第一个classes的. 但是什么情况下会带源码包+JavaDoc呢:
- 多是这个项目你自己官方下了完整的java源码 ,源码是有完整的java注释的.
- 你修改了其中的部分代码. 版本改为了
janus-0.2.1.x.然后重写通过mvn install放到了本地的.m2仓库中.此时的文件其实是janus-0.2.1.x.jar,但是它是不包含了注释的. - 你在另一个Java项目中引用了
janus-0.2.1.x.jar,然后查看源码的时候, IDEA发现你没有这个包的源码,会自动使用反编译工具查看.class, 然后提示你下载源码 ,你很随手的点了一下,发现下好后注释出来了,很开心. (其实下的是janus-0.2.1-javadoc.jar,因为IDEA找不到你的版本,找了最近的..?) - 不开心的时候就是, 当你debug的时候, 实际使用的是
0.2.x.jar, 但是文档注释却是0.2.1.jar的,当然就会出现行数对不上,导致跳转错误的情况了…
解决办法很简单:
- 首先, 在IDEA的
Libraries中删去你从mvn网络下载的的官方sources+javadoc.jar - 然后 ,在你需要修改过的项目源文件路径下, 执行
mvn source:jar install -DskipTests,这样就会通过mvn插件打出匹配的源码包, 然后安装到你的.m2(mvn本地仓库中了) - 最后, 重新在IDEA的
Libraries中选择正确的源码包问题就解决了~
补充: 如果你第一次看jar源码的时候, 可能还是.class ,这时候它提示你导入source/javadoc. 注意手动选择,而不要点自动下载.
Maven私有仓库
简单理解就是harbor(镜像管理仓库)类似,一个团队里为了避免每个人都在不同版本号跟网络环境折腾下载的问题,把mvn中的远程仓库从mvn官方换成内网服务。
问题在于,如何构建nexus,如何保证高效维护,最后开发机器如何配置连接。
更新, 因为nexus 太老旧, 加上新的JFrog的artifactory我觉得设计的比Nexus要好不少, UI也好看很多, 所以已经转到使用JFrog上了, 详情参考单独的问题
0x05.后续想法
其实Java的使用也是经过了否定之否定的过程…中间细节略过,有个很重要的问题我一直在想:
Java如此重度依赖各种工具插件,自动补全等. 那离开了IDE,如何生存呢… (有人说用vim类+插件,我从体验VSCODE来看就知道想多了..除非你纯写小demo,而用Java写小程序就更是滑稽了).
工具本质是**提高做事效率 .*我想不会有人为了使用XML,JSON,YAML 去单独看一本Oxxly的厚书. 也不会有人愿意用一下ant,mvn,gradle 或者svn,git 就又去钻很久各种细节. 所以的确如老王所说,Docker这种容器化技术之所以能在Unix系崛起如此之快… 的确是因为大家更喜欢Win这种开箱即用,而不是到处重复折腾环境,折腾编译,折腾部署.*
19.9更新补充: 有一篇文章写专门写了下maven的进阶用法, 还挺实用, 前面的地方可以参考一下(包括多线程编译/跳到指定模块开始)
参考资料:
- 导入mvn项目总是报错
- 各种docs..
待后续遇到具体问题更新,也欢迎反馈