大家好,又见面了,我是你们的朋友全栈君。
前言:
最近工作中遇到了几次跟maven打包相关的问题,每个问题上网查资料解决都花了不少时间,很影响工作进度。既然遇到好几次,每次都能发现知识盲点,干脆总结整理一下,啃掉这个难啃的骨头。
ps:最近看到了一个很有意思句子:因为今天不想跑步,所以才去跑,这是长距离跑者的思维方式。
转载:
正文:
还是首先描述一下最近遇到的几个问题吧:
一、初见
springboot多模块项目mvn打包遇到的问题 – 存在依赖但却无法发现符号
这个描述跟我遇到的问题差不多,简单说就是AB两个工程是同一个父工程(X)下的子工程,因为A是一个springboot项目,所以父工程X就把parent设置成了:
org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE
并且在A项目中配置了打包插件:
org.springframework.boot
spring-boot-maven-plugin
true
然后AB的都是X。另一个同事开发A,在里边写了一个XXXUtil类。我开发B,为了使用XXXUtil,于是在B的dependency里依赖了A。
本地测试正常,然后就打算mvn install一下,结果就报错:
[INFO] ————————————————————————
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project main-jar: Compilation failure: Compilation failure:
[ERROR] /Users/zhaohui/workspace/Projects/IDEA/packing-test/main-jar/src/main/java/com.zh/Main.java:[3,25] 程序包com.zh.sbt.common不存在
[ERROR] /Users/zhaohui/workspace/Projects/IDEA/packing-test/main-jar/src/main/java/com.zh/Main.java:[8,51] 找不到符号
[ERROR] 符号: 变量 Common2
[ERROR] 位置: 类 com.zh.Main
[ERROR] -> [Help 1]
[ERROR]
奇怪了,测试的时候明明可以,怎么打包的时候就找不到类了呢?于是就打开A项目打出来打jar包,看一下里边是不是真的没有这个类:
zhaohuideMacBook-Pro:target zhaohui$ jar vtf spring-boot-test-1.0-SNAPSHOT.jar
…//此处省略部分输出
350 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/common/Common2.class
347 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/common/Common.class
822 Thu Feb 28 23:15:32 CST 2019 BOOT-INF/classes/com/zh/sbt/Main.class
…//此处省略部分输出
发现,包内的文件夹路径跟我项目的文件夹路径不一致,用luyten-0.5.3反编译代码,发现代码里的package行没有变化,所以springboot有可能使用了自定义的类加载器,把类加载器的根目录设置为了BOOT-INF/classes/,而maven打包的时候,使用的类加载器根目录就是项目根路径,所以才找不到类。
既然是A项目打包的问题,那直接把A项目的标签注释掉不就行了。说干就干,修改完果然可以正常打包了。
然后就是提交代码,部署,结果A项目启动不起来了,报错如下:
zhaohuideMacBook-Pro:target zhaohui$ java -jar spring-boot-test-1.0-SNAPSHOT.jar
spring-boot-test-1.0-SNAPSHOT.jar中没有主清单属性
其实到这里思路已经比较混乱了,为什么spring-boot的打包插件能修改文件路径?为什么不用spring-boot插件就找不到主属性清单?应该用什么打包插件呢?有哪些打包插件呢?每个插件有什么区别呢?
今天太累了,我不想跑步了。。。
面对这么多疑问,大概就是这个感觉。具体怎么解决暂且不表,第一次遇到这个问题,也没有想明白这么多疑问,结果没想到,第二天,又遇到了打包的问题,而且这次的问题更让我郁闷。且听我慢慢道来… …
二、重逢
接下来就说说我的B项目,因为项目的任务是通过程序往hadoop集群提交一个mr任务,B项目的代码特别简单,就是调用yarn的api提交一个任务。
本地测试也没有问题,我就想把代码放到线上跑一下。因为jar包需要很多依赖,就想着直接把所有的依赖都打到一个jar文件里,这样就不用上传一堆依赖jar包了。于是我使用了这个打包插件:
org.apache.maven.plugins
maven-shade-plugin
3.1.1
package
shade
implementation=”org.apache.maven.plugins.shade.resource.ManifestResourceTransformer”>
com.zh.Main
有了昨天的经验,在plugin里边配置里主类,应该没有问题吧,于是执行了一下jar包,然后就报了一个奇怪的错误:
[WARN ] 2019-02-28 23:59:26 [main] o.a.hadoop.util.NativeCodeLoader – Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
[ERROR] 2019-02-28 23:59:26 [main] c.k.dp.dataexchange.manager.Main – Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
java.io.IOException: Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
at org.apache.hadoop.mapreduce.Cluster.initialize(Cluster.java:120)
at org.apache.hadoop.mapreduce.Cluster.(Cluster.java:82)
at org.apache.hadoop.mapreduce.Cluster.(Cluster.java:75)
at org.apache.hadoop.mapreduce.Job$9.run(Job.java:1260)
at org.apache.hadoop.mapreduce.Job$9.run(Job.java:1256)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1657)
at org.apache.hadoop.mapreduce.Job.connect(Job.java:1255)
at org.apache.hadoop.mapreduce.Job.submit(Job.java:1284)
at com.kuaishou.dp.dataexchange.manager.Main.main(Main.java:95)
Exception in thread “main” java.lang.RuntimeException: 集群启动发生异常,异常信息Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.
at com.kuaishou.dp.dataexchange.manager.Main.main(Main.java:102)
这报错信息是什么鬼。。。说明内容一点都不具体,看不懂,只能找到报错的代码:
private void initialize(InetSocketAddress jobTrackAddr, Configuration conf) throws IOException {
ServiceLoader var3 = frameworkLoader;
synchronized(frameworkLoader) {
Iterator i$ = frameworkLoader.iterator();
while(i$.hasNext()) {
ClientProtocolProvider provider = (ClientProtocolProvider)i$.next();
LOG.debug(“Trying ClientProtocolProvider : ” provider.getClass().getName());
ClientProtocol clientProtocol = null;
try {
if (jobTrackAddr == null) {
clientProtocol = provider.create(conf);
} else {
clientProtocol = provider.create(jobTrackAddr, conf);
}
if (clientProtocol == null) {
LOG.debug(“Cannot pick ” provider.getClass().getName() ” as the ClientProtocolProvider – returned null protocol”);
} else {
this.clientProtocolProvider = provider;
this.client = clientProtocol;
LOG.debug(“Picked ” provider.getClass().getName() ” as the ClientProtocolProvider”);
break;
}
} catch (Exception var9) {
LOG.info(“Failed to use ” provider.getClass().getName() ” due to error: “, var9);
}
}
}
if (null == this.clientProtocolProvider || null == this.client) {
throw new IOException(“Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond server addresses.”);
}
}
先是debug,打断点,发现不打包执行没有问题,这就比较蛋疼了,排查问题都不好排查。还好有一些debug的日志可以参考。于是执行参数增加 -Xdebug,logback日志级别改成DEBUG,再次执行jar包,中间细节不再赘述,总之找到出问题的代码在这里:
//java.util.ServiceLoader line 338 hasNextService()方法//代码逻辑简单说就是Enumeration configs = ClassLoader.getSystemResources(“META-INF/services/” ClientProtocolProvider.class.getName());
//如果不打包,获取到的configs size为2,打印出来就是://org.apache.hadoop.mapred.LocalClientProtocolProvider//org.apache.hadoop.mapred.YarnClientProtocolProvider//如果打包,获取到的configs size为1,打印出来就是://org.apache.hadoop.mapred.LocalClientProtocolProvider
搞了半天,打包修改了META-INF/services/里的内容,所以才导致了报错。
ps:吐槽一下hadoop报错信息,完全没有描述出错的问题,导致排查浪费了很多时间。
说实话,解决这个问题,并没有很开心,一方面花了太多时间,另一方面,这次的问题给我带来了更多的困扰:META-INF里边n多东西都是干什么的?打包的时候如何处理META-INF这个文件夹?
最让我奇怪的是,我总共就配置了一个plugin,结果target里边打出来了三个包[xxx.jarxxx-shade.jarxxx-source.jar]除了shade以外,其他两个jar为什么会打出来呢?
三、回首
打包给自己的工作带来了这么多困扰,归根结底还是不知道maven到底是怎么打包的,所以遇到了具体的问题就不知道该怎么分析解决。所以这次正好整理了一下思路。虽然没有给出上边问题的具体解决方式,但是能够把思路说明白,后续再慢慢分析解决问题吧。
思考这个问题的入口其实就是maven的这个标签,具体配置在里边的东西都起到了什么作用呢?很幸运的找到了这篇文章maven内部运行原理解析
具体细节我就不描述了,看到这里,我的疑问是,文章中提到,每一个plugin,都要有一个和表明该插件是在哪个阶段执行的哪个方法。我的pom里边并没有配置这些,插件也照样能生效,那我怎么知道具体每个插件的这两个配置项呢?另外如果我一个plugin都没有配置,也正常打包了,这个时候使用的是什么配置呢?
在maven中,所有的PO都有一个根对象,就是Super POM。Super POM中定义了所有的默认的配置项。Super POM对应的pom.xml文件可以在maven安装目录下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到
第一个问题我在这个帖子里找到了解决的方法maven常用插件解析 :
maven-help-plugin
maven-help-plugin是一个小巧的辅助工具。
最简单的help:system可以打印所有可用的环境变量和Java系统属性。
help:effective-pom和help:effective-settings最为有用,它们分别打印项目的有效POM和有效settings,有效POM是指合并了所有父POM(包括Super POM)后的XML,
当你不确定POM的某些信息从何而来时,就可以查看有效POM。
有效settings同理,特别是当你发现自己配置的settings.xml没有生效时,就可以用help:effective-settings来验证。
此外,maven-help-plugin的describe目标可以帮助你描述任何一个Maven插件的信息,还有all-profiles目标和active-profiles目标帮助查看项目的Profile。
所以执行mvn help:effective-pom就可以列出所有的配置项,我对空项目执行了一下这个命令,把默认的所有插件整理了一下,总结如下:
//知乎怎么还不支持表格
| parse | plugin | goal |
| —— | —— | —— |
| clean | maven-clean-plugin | clean |
| process-test-resources | maven-resources-plugin | testResources |
| process-resources | maven-resources-plugin | resources |
| package | maven-jar-plugin | jar |
| compile | maven-compiler-plugin | compile |
| test-compile | maven-compiler-plugin | test-compile |
| test | maven-surefire-plugin | test |
| install | maven-install-plugin | install |
| deploy | maven-deploy-plugin | deploy |
| site | maven-site-plugin | site |
| site-deploy | maven-site-plugin | deploy |
这里出现了maven内部运行原理解析中没有提到的几个parse:[clean/site/site-deploy]这几个的含义在这个帖子找到了答案:maven的三大生命周期
现在知道了具体每个阶段执行的是哪个方法,剩下的只要获取插件的代码就能完整的分析整个打包流程了,代码地址:查看maven插件的源码
至此,整个思路就理清楚了。即使没有把这块硬骨头啃下来,至少已经放进锅里了~~
附录:
最开始查资料的时候,没有搞清楚打包和压缩是两件事,查了一些与压缩相关的内容,其中这个帖子很有意思:RAR和ZIP:压缩大战真相
向菲利普·卡兹致敬!!
总结:
最近工作接触了很多新东西,也遇到了各种个样的问题。天天一边解决问题,还要赶项目进度,每天都要搞到很晚,每当要开始整理一些东西的时候,总是想着:今天太累了,不搞了吧,早点休息养精蓄锐,明天总结。结果天天如此,一拖就是一个月。
因为今天不想跑步,所以才去跑,这是长距离跑者的思维方式。
而我想成为长距离跑者!
以上
最后,让我们保持独立思考,不卑不亢。长成自己想要的样子! (引用自 我非常喜欢的B站up主 ”独立菌儿“->猛戳链接
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/144013.html原文链接:https://javaforall.cn