在开发中,无论是在微服务架构下还是单体架构下,我们经常使用到多模块开发,其中必然有抽取公共模块,将重复的依赖和重复的工具类整合的一个过程。
但在我们打包时,我们经常会莫名其妙地出现can't find sympol的问题。明明在java类的import语句中正常导入了正确路径的类,也没有爆红,为什么会找不到类呢?这主要是由于我们在对common模块打包时jar包的属性以及结构导致的原因。
1.首先我们要理解maven工程是如何调用我们在pom.xml文件中声明的依赖的。
当我们编译maven项目时,maven工程会去我们本地仓库,根据坐标找到对应的jar包,然后将其复制到本包下的BOOT-INF/libs下,如图:
我们在本项目中引入了springboot框架的web模块,那我们来到本地maven仓库:
可以得知,groupId,artifactId,version三者由高到低组成了maven寻找依赖对应jar包的整个路径,其中这些内容都是在我们导入依赖的时候从中心仓库下载得来。
而我们项目中只要引入了dependency依赖,我们就会根据坐标找到对应jar包,复制到本包下。那我们java类又是如何导入jar包的类呢?
2.我们要了解一个普通JAR包的基本内容
以Netflix-Eureka为例
我们可以看到其具体结构如下:
1)其中META-INF存放本jar包的配置信息,如pom.xml,pom.properties等:
2)org则是其工程下的一个jar子包,其最底层才是我们想要使用的java类:
3)那么我们在java文件中导包的过程,实际上就是导入其类所在的路径:
这个了解之后,我们就知道我们在项目中如何进行导包的了,距离解决我们的问题也接近了。
3.然后,我们要了解spring-boot打成的我们项目可执行jar包的结构
使用7-ZIP打开可以清楚看到,我们的可执行JAR包中主要由两个文件夹,一个是BOOT-INF,一个是META-INF。
上面我们说到,META-INF文件夹是存放一些配置,org则是本文件夹下的一部分jar包。那么我们如何运行这个可执行jar包的呢?可执行jar包和普通jar包的区别在何处呢?
就在BOOT-INF
1)如何启动可执行jar包的
首先我们先查看一下BOOT-INF,其下的结构是这样:
我们点进去后会发现,我们编写的代码在classes文件夹下,我们的主启动程序也在其之下:
看到这里大家应该能把jar包内容和项目结构完全对应(resources文件夹本质上就是根文件夹,其下的内容就在根路径下,这也是为什么比如mybatis对应的xml文件路径配置前通常我们会加上"classpath:"的原因)
所以启动可执行jar包的过程,本质上就是我们去BOOT-INF/classes/去启动我们主启动类的过程。
2)libs文件夹下则存放着我们我们找到的依赖:即标题1中找到的jar包,将其复制到了这里
到这里,我们基本摸清了maven的整个流程,可以解决我们的问题了。
4.为什么我们在IDEA里面明明没有报错,使用mvn compile命令后,却找不到类呢?
直接上答案:因为你把common模块打成了可执行jar包,其具有BOOT-INF文件夹。
那达成可执行jar包为什么会影响我调用common模块下的类呢?
因为我们调用Jar包默认都是在jar包根路径下调用:
再把图贴出来方便大家看:
而一旦我们把项目达成可执行jar包,在标题3中我们讲的,我们项目内的类就会在BOOT-INF/classes下:
所以此时,我们common模块的类并没有在jar包根目录下,而是在BOOT-INF/classes下。
但是我们java导包默认地是去包的根路径下,而根路径此时啥都没有:
所以就找不到当前类
5.那如何解决java寻找jar包类的寻找过程问题呢?
很简单哇,将common模块变成普通jar包,没有BOOT-INF,其内容都在jar包根路径下不就能过正常找到类了吗?
工作:替换common模块的pom.xml下的打包插件,将spring-boot-maven-plugin下添加一行配置:
skip标签就能跳过BOOT-INF的打包过程,执行完后:
1)先install common包
2)更新全部依赖
最后我们的结构是这样的:
消费者模块中引入了common模块:
我们的common jar包,就理所应当应该在BOOT-INF/libs/下,我们去仓库寻找:
打开后,我们发现,此时common模块没有了BOOT-INT文件夹,其原本所有的类均在其子路径下:
二者对应:这是jar包的结构
这是我们项目的结构
我们就可以成功一一对应!
到此结束!