1. 程式人生 > >SpringBoot專案打包蘿蔔與坑

SpringBoot專案打包蘿蔔與坑

一、SpringBoot專案,打成預設的Jar包

應用背景:
該場景較為常見,沒有特殊需求時,都可以採用這種打包方式

操作步驟:
SpringBoot專案打成預設的Jar包是最簡單的一種打包方式,預設情況下,我們幾乎不需要增加任何額外的配置,只要我們執行相關的打包命令即可(maven)mvn clean package就可以完成相應的打包操作;這時,maven會幫助我們將我們的程式碼和依賴庫打成一個Jar包,包名在pom.xml檔案中進行配置。打包完成後,我們之間執行java -jar <package.jar> 即可。

補充:SpringBoot打包後的目錄格式如下:
SpringBoot Jar

看著東西挺多了,其實我們只要關注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包的名稱是帶時間戳的,這樣就會導致最終找不到類。參考下圖所示:
snapshot 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>,專案成功執行,且無異常
projectZIP

五、補充(之前踩過的坑)

由於專案本質是一個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值帶時間