SpringBoot项目打包萝卜与坑
一、SpringBoot项目,打成默认的Jar包
应用背景:
该场景较为常见,没有特殊需求时,都可以采用这种打包方式
操作步骤:
SpringBoot项目打成默认的Jar包是最简单的一种打包方式,默认情况下,我们几乎不需要增加任何额外的配置,只要我们执行相关的打包命令即可(maven)mvn clean package
就可以完成相应的打包操作;这时,maven会帮助我们将我们的代码和依赖库打成一个Jar包,包名在pom.xml文件中进行配置。打包完成后,我们之间执行java -jar <package.jar>
即可。
补充:SpringBoot打包后的目录格式如下:
看着东西挺多了,其实我们只要关注2个点即可:第一是目录BOOT-INF,第二是META-INF
二、SpringBoot项目,将源码与依赖库分离打包
应用背景:
在某些时候,我们可能会有一些需求,比如将项目的源码与依赖库和配置分别打包,期望各自相互独立。
真实案例:
58同城自主研发了RPC框架"SCF",我们使用SpringBoot调用SCF相关的服务,然后做相关处理和业务逻辑,这都不是重点;重点是SCF在启动时要对所有其依赖的Jar包进行扫描,如果没能扫描到,那就SCF的类加载器将无法加载这些Jar包,并且最重要的来了, SCF的扫描是基于文件系统路径的 ,也就是说,此时,如果我们按照第一种方式打包的话,SCF在去扫描其依赖的Jar包时,真实的Jar包是在我们打好的工程Jar包里面——我习惯把它称之为“jar中jar”,这时候按照文件系统路径去扫描的时候,是无法加载到这些Jar的。最直观的结果就是我们的工程打包之后无法运行。那么此时我们要如何解决这个问题呢?
问题定位:遇到实现方式为基于文件系统扫描时,遇到了jar中Jar这种情况,就将无法正确加载相关的类
问题分析:避免Jar中Jar,我们需要避免Jar中Jar找不到的情况
解决方案:1.打包时分离源码与类库及配置,保证加载的东西都是在文件系统路径下可寻找的。2.如果是Web项目考虑打成war包,war包在部署到web容器中后,会自动解压,也就变成了单层Jar,也能避免Jar中Jar
如何分离打包呢?
一、首先引入Maven的Assembly插件maven-assembly-plugin
,配置细节如下:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
然后创建一个assembly.xml配置文件,增加我们打包的细节配置的描述:
1.我们把最终的工程打成一个ZIP包
2.这个ZIP包中包含了几个元素: 打包格式,我们选择的是ZIP,ZIP里包含的是我们的配置文件,我们工程的配置文件一般放在src/main/resources中,所以这里我们把这个文件夹下的全部配置都打加进来,输出的包名为config,bin,lib同理,lib中包含了全部的依赖Jar
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>package</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>*.*</include>
</includes>
<filtered>true</filtered>
<outputDirectory>${file.separator}config</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/resources/runScript</directory>
<outputDirectory>${file.separator}bin</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>${file.separator}lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>${file.separator}</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
二、引入maven-jar-plugin
插件;这里要指定启动类,<useUniqueVersions>false</useUniqueVersions>
这个配置是一个非常关键的配置,此处存在着一个大深坑,如果你没遇到过的话…请参考,如果不配置此项,默认值为true,在快照发布后,我们download到本地仓库的Jar包会出现2个,一个是带时间戳的,一个不带,我们引用的是不带时间戳的,但是下载的时候会下载成带时间戳的,这是因为在打包的时候,META-INF中的classpath配置里,引用的Jar包的名称是带时间戳的,这样就会导致最终找不到类。参考下图所示:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.package.SpringbootPassportApplication</mainClass>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</plugin>
三、引入依赖管理插件maven-dependency-plugin
,这个插件主要作用是分类依赖库,简单是说就是把我们依赖的Jar包(lib)打在工程外面,路径就是在${project.build.directory}/lib
中,注意:includeScope,如果配置为compile,那么项目依赖的运行时依赖是不会被打包的,在运行时可能会出现类找不到或者其它的异常,所以想要避免这个问题,需要把includeScope配置为runtime。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-lib</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>runtime
</includeScope>
</configuration>
</execution>
</executions>
</plugin>
四、结果
执行打包命令后,ZIP包内容如下所示:,我们解压后,运行java -jar <project.jar>
,项目成功运行,且无异常
五、补充(之前踩过的坑)
由于项目本质是一个web项目,此处只是打了Jar包,所以一定要注意的是,在引入项目依赖的时候,不可以排除spring-boot-starter-web
中对内置tomcat的依赖,也不可以将spring-boot-starter-tomcat
中的scope设置为provided,因为我们打Jar包执行时需要依赖相关的web容器。——有人会问为什么要排除这个依赖,是因为在尝试打war包的时候,对这些配置进行排除,然后切换成Jar后,发现项目运行不起来,排查了很久,浪费了不少时间,也算是自己给自己挖的坑,希望大家在切换的时候避免这个问题。
文章总结:
0.SpringBoot单独打包无法加载Jar包中的配置和Jar
1.SpringBoot分离打包的方法
2.SpringBoot分离打包需要的Maven依赖和配置文件
3.SpringBoot引入快照版本的依赖打包时,META-INF中寻找的classpath值带时间戳