最近项目引入了新的大数据组件,在开发过程遇到了好多次jar的问题,排包排到烦躁,这里真心想说一句IDEA流弊啊!!!
本文对遇到的问题和最近看的maven机制做一个总结,希望以后能尽量避免遇到jar包冲突问题。项目开发工具是IDEA2018.2,Maven3.0.4。
本文先简单介绍下遇到的Jar包冲突问题以及问题的原因,然后对原因进行解释,这样看的会更加明白一点。最后记录IDEA下的两种打包方式,对开发独立的小工具会有帮助。
Maven项目中,本人遇到的Jar包冲突问题有如下两种:
(“2”遇到过这种情况,代码在本地跑正常,打包成-with-dependencies整包扔到服务器,就跑到其他类里面去了,猜测是和Maven的整包打包机制或者是底层文件系统的顺序有关,具体原因暂时不清楚…)
日志中一般会报如下错误:
引起上面Jar包冲突本质原因是如下两点:
1.maven的依赖调解机制(即距离优先和声明优先原则,下文会说到)。
2.JVM中,classloader只会加载相同的class一次,以后加载的class不会被覆盖!
接下来,本文分别对两种原因进行介绍,如果说的不对,麻烦指正下。
Maven坐标用于唯一标识一个jar包,通过groupId、artifactId、version、packaging、classfier这些元素来定义,前三个是必须的,packaging默认为jar,classfier不太清楚,举个例子:
用于标识jar包在哪个阶段被用到。把第三方jar包放到classpath中,才能调用jar包中的方法,maven把classpath分成三种:
1. compile -- 默认是这种,编译、测试和运行的时候都有效
2. test – 仅在测试时生效
3. runtime – 测试和运行时都有效
加入A依赖与B,B依赖与C,那么在项目A中引用B是,C会自动引进来。例如hbase-server是项目中引入的依赖,hbase-server又依赖了其他jar包,这些jar包会自动导入进来。
maven2.0.8之后引入了依赖调解机制。照搬书上的一个例子,项目A有如下的依赖关系:
那么最终哪个X会被maven解析使用,maven规定了如下两个原则:
原则1:路径最近优先
原则2:原则1不能解决问题时,Pom文件中第一声明优先
从上面可以看出,X2.0的路径比较短,所以maven会解析X2.0
个人觉得,原则1不太是个人能够控制的,但是根据原则2,将log4j之类的jar包声明放在pom文件dependency开头的地方还是蛮有必要的,log4j-jar包冲突日志都打印不出来!
就是将依赖声明为optional,这样其他项目依赖与本项目时,这些依赖是不会被传递的。
书上也提到了其实应该避免这种情况,我是没用过,这里也不细究了,以后用到再说。
项目中,服务最终是运行在tomcat容器中的,所以Jar包的加载顺序和JVM的类加载机制还有Tomcat容器有关。网上看到的Tomcat的jar包加载顺序,对其做了一些修改,加载顺序如下:
1. $java_home/lib 目录下的java核心api (JVM的Bootstrap ClassLoader)
2. $java_home/lib/ext 目录下的java扩展jar包(JVM的Extension ClassLoader)
3. java -classpath/-Djava.class.path所指的目录下的类与jar包(JVM的Application ClassLoader)
4. $CATALINA_HOME/common目录下加载
5. $CATALINA_HOME/server目录下加载
6. $CATALINA_BASE/shared目录下加载
7. 我们的项目路径/WEB-INF/classes下的class文件
8. 我们的项目路径/WEB-INF/lib下的jar文件
(ps:在同一个文件夹下,jar包加载的顺序就和底层文件系统的排序有关了,所以可能出现在windows下没问题,在linux下有问题的情况)
如果一定要在同一个项目引用同一个jar包的不同版本,咋办?
这里说的是类限定名形同的情况。按照JVM的加载机制,classLoader对于同一个class只会加载一次。网上找了找,有两种说法,一种是自己修改jar包源码…;另外一种是自己写个ClassLoader,后续搞一搞,感觉蛮有意思的。
本人一般都是看下报错信息,然后双shift找下类在哪些jar包中出现,然后排掉就好了。IDEA自带了可视化的Maven Tree,非常流弊!
参考:
《Maven实战》
https://blog.csdn.net/varyall/article/details/81149644(tomcat Jar包加载顺序)