使用Assembly打包和部署SpringBoot工程方式
1、Spring Boot項(xiàng)目的2種部署方式
目前來說,Spring Boot 項(xiàng)目有如下 2 種常見的部署方式。
1、一種是使用 docker 容器去部署。將 Spring Boot 的應(yīng)用構(gòu)建成一個(gè) docker image,然后通過容器去啟動(dòng)鏡像。這種方式在需要部署大規(guī)模的應(yīng)用以及對(duì)應(yīng)用進(jìn)行擴(kuò)展時(shí),是非常方便的,屬于目前工業(yè)級(jí)的部署方案,但是需要掌握 docker 的生態(tài)圈技術(shù)。
2、另一種則是使用 FatJar 直接部署啟動(dòng)(將一個(gè) jar 及其依賴的三方 jar 全部打到一個(gè)包中,這個(gè)包即為 FatJar)。這是很多初學(xué)者或者極小規(guī)模情況下的一個(gè)簡(jiǎn)單應(yīng)用部署方式。
2、Assembly 的優(yōu)勢(shì)
上面介紹的 Fatjar 部署方案存在以下缺陷。
1、如果直接構(gòu)建一個(gè) Spring Boot 的 FatJar 交由運(yùn)維人員部署的話,整個(gè)配置文件都被隱藏到 jar 中,想要針對(duì)不同的環(huán)境修改配置文件就變成了一件很困難的事情。
2、如果需要啟動(dòng)腳本啟動(dòng)項(xiàng)目的時(shí)候,這種直接通過 jar 的方式后續(xù)會(huì)需要處理很多工作。
而通過 assembly 將 Spring Boot 服務(wù)化打包,便能解決上面提到的 2 個(gè)問題。
1、使得 Spring Boot 能夠加載 jar 外的配置文件。
2、提供一個(gè)服務(wù)化的啟動(dòng)腳本,這個(gè)腳本一般是 shell 或者 windows 下的 bat ,有了 Spring Boot 的應(yīng)用服務(wù)腳本后,就可以很容易的去啟動(dòng)和停止 Spring Boot 的應(yīng)用了。
3、項(xiàng)目配置
3.1、添加插件
編輯項(xiàng)目的 pom.xml 文件,加入 assembly 打包插件。
<build> <!-- 指定需要打包編譯的文件 --> <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> <plugins> <!-- 指定啟動(dòng)類,將依賴打成外部jar包 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.5</version> <configuration> <!-- 不打包配置文件 --> <excludes> <exclude>*.xml</exclude> <exclude>*.properties</exclude> <exclude>*.yml</exclude> </excludes> <archive> <!-- 生成的jar中,不要包含pom.xml和pom.properties這兩個(gè)文件 --> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <!-- 是否要把第三方j(luò)ar加入到類構(gòu)建路徑 --> <addClasspath>true</addClasspath> <!-- 外部依賴jar包的最終位置 --> <classpathPrefix>../lib</classpathPrefix> <!-- 項(xiàng)目啟動(dòng)類 --> <mainClass>com.example.TestApplication</mainClass> </manifest> <manifestEntries> <!--MANIFEST.MF 中 Class-Path 加入配置文件目錄--> <Class-Path>../config/</Class-Path> <Implementation-Title>${project.artifactId}</Implementation-Title> <Implementation-Version>${project.version}</Implementation-Version> <Build-Time>${maven.build.timestamp}</Build-Time> </manifestEntries> </archive> </configuration> </plugin> <!-- 拷貝配置文件到config目錄下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.7</version> <executions> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>*.xml</include> <include>*.properties</include> <include>*.yml</include> </includes> </resource> </resources> <outputDirectory>${project.build.directory}/config</outputDirectory> </configuration> </execution> </executions> </plugin> <!-- 將依賴jar包拷貝到lib目錄下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> <executions> <execution> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> <!-- 打包插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4.1</version> <configuration> <finalName>${project.artifactId}</finalName> <appendAssemblyId>false</appendAssemblyId> <descriptors> <!--具體的配置文件--> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <!--綁定到maven操作類型上--> <phase>package</phase> <!--運(yùn)行一次--> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- 打包時(shí)跳過測(cè)試 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build>
從上面代碼可以看出,把 assembly 的配置都放在 main/assembly 目錄下(具體目錄里面的文件接下來會(huì)創(chuàng)建)。
3.2、編寫服務(wù)啟動(dòng)/停止/重啟腳本
在 assembly 目錄下創(chuàng)建一個(gè) bin 文件夾,然后在該文件夾下創(chuàng)建 start.sh 文件,這個(gè)是 linux 環(huán)境下的啟動(dòng)腳本,具體內(nèi)容如下。
Tip:開頭的項(xiàng)目名稱、jar 包名稱不用我們手動(dòng)設(shè)置,這里使用參數(shù)變量,在項(xiàng)目打包后這些參數(shù)自動(dòng)會(huì)替換為 pom 的 profiles 中 properties 的值(assembly 配置文件需要開啟屬性替換功能),下面另外兩個(gè)配置文件也同理。
#!/bin/bash # 項(xiàng)目名稱 SERVER_NAME="${project.artifactId}" # jar名稱 APPLICATION="${project.build.finalName}" # 進(jìn)入bin目錄 cd `dirname $0` # bin目錄絕對(duì)路徑 BIN_PATH=`pwd` # 返回到上一級(jí)項(xiàng)目根目錄路徑 cd .. # 打印項(xiàng)目根目錄絕對(duì)路徑 # `pwd` 執(zhí)行系統(tǒng)命令并獲得結(jié)果 BASE_PATH=`pwd` # 外部配置文件絕對(duì)目錄,如果是目錄需要/結(jié)尾,也可以直接指定文件 # 如果指定的是目錄,spring則會(huì)讀取目錄中的所有配置文件 CONFIG_PATH=$BASE_PATH/config LOG_PATH=$BASE_PATH/logs/${SERVER_NAME} JAVA_OPT="-server -Xms1024m -Xmx1024m -Xmn512m" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" APPLICATION_JAR=$(ls $BASE_PATH/boot/$APPLICATION.jar) PROCESS_ID=$(ps -ef|grep $BASE_PATH/boot/$APPLICATION|grep -v grep|awk '{print $2}') if [ ! -n "$PROCESS_ID" ];then echo "$SERVER_NAME服務(wù),進(jìn)程正在啟動(dòng)中,請(qǐng)稍等..." file="nohup.out" if [ ! -f "$file" ]; then touch "$file" fi source /etc/profile nohup java ${JAVA_OPT} -jar ${APPLICATION_JAR} > /dev/null & tail -f "${LOG_PATH}/system/info.log" else echo "$SERVER_NAME服務(wù),進(jìn)程(id:$PROCESS_ID)已存在,啟動(dòng)失敗" fi
創(chuàng)建 stop.sh 文件,這個(gè)是 linux 環(huán)境下的停止腳本,具體內(nèi)容如下。
#!/bin/bash # 項(xiàng)目名稱 APPLICATION="${project.artifactId}" # 項(xiàng)目啟動(dòng)jar包名稱 APPLICATION_JAR="${project.build.finalName}.jar" # 通過項(xiàng)目名稱查找到PID,然后kill -9 pid PROCESS_ID=$(ps -ef|grep "${APPLICATION_JAR}" |grep -v grep|awk '{print $2}') if [[ -z "$PROCESS_ID" ]] then echo "$APPLICATION服務(wù)沒有啟動(dòng),請(qǐng)進(jìn)一步驗(yàn)證是否需要停止進(jìn)程" else echo "$APPLICATION服務(wù),進(jìn)程已存在,正在kill進(jìn)程,進(jìn)程ID:$PROCESS_ID" kill -9 ${PROCESS_ID} echo "$APPLICATION服務(wù),進(jìn)程$PROCESS_ID停止成功" fi
創(chuàng)建 restart.sh 文件,這個(gè)是 linux 環(huán)境下的重啟腳本,具體內(nèi)容如下。
#!/bin/bash # 項(xiàng)目名稱 APPLICATION="${project.artifactId}" # 進(jìn)入bin目錄 cd `dirname $0` # bin目錄絕對(duì)路徑 BIN_PATH=`pwd` echo "$APPLICATION服務(wù)正在停止" sh $BIN_PATH/stop.sh echo "$APPLICATION服務(wù)正在重啟" sh $BIN_PATH/start.sh
創(chuàng)建 server.sh 文件,這個(gè)是 linux 環(huán)境下根據(jù)指令執(zhí)行服務(wù)啟動(dòng)、停止、重啟、查看狀態(tài)的腳本,具體內(nèi)容如下。
#!/bin/bash # 項(xiàng)目名稱 SERVER_NAME="${project.artifactId}" # jar名稱 APPLICATION=${project.build.finalName} # 進(jìn)入bin目錄 cd `dirname $0` # bin目錄絕對(duì)路徑 BIN_PATH=`pwd` # 返回到上一級(jí)項(xiàng)目根目錄路徑 cd .. # 打印項(xiàng)目根目錄絕對(duì)路徑 # `pwd` 執(zhí)行系統(tǒng)命令并獲得結(jié)果 BASE_PATH=`pwd` # 外部配置文件絕對(duì)目錄,如果是目錄需要/結(jié)尾,也可以直接指定文件 CONFIG_PATH=$BASE_PATH/config APP_NAME=$BASE_PATH/boot/$APPLICATION.jar LOG_PATH=$BASE_PATH/logs/${SERVER_NAME} JAVA_OPT="-server -Xms512m -Xmx1024m -Xmn512m" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" #判斷用戶是否為lbs loginUser() { USER=`whoami` if [ ${USER} != "lbs" ];then echo "Please use user 'lbs'" exit 1 fi } loginUser # 使用說明,用來提示輸入?yún)?shù) usage() { echo "Usage: sh server.sh [start|stop|restart|status]" exit 1 } # 檢查程序是否在運(yùn)行 is_exist() { # 根據(jù)關(guān)鍵字過濾進(jìn)程PID,關(guān)鍵字由業(yè)務(wù)方自定義 pid=$(ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}') if [ -z "${pid}" ]; then return 1 else return 0 fi } # 啟動(dòng)程序 start() { # 啟動(dòng)程序時(shí),可酌情根據(jù)各自服務(wù)啟動(dòng)條件做出相應(yīng)的調(diào)整 is_exist if [ $? -eq "0" ]; then echo "${SERVER_NAME}服務(wù)進(jìn)程已存在,pid=${pid}" else if [ ! -f "$LOG_PATH" ]; then touch "$LOG_PATH" fi source /etc/profile nohup java $JAVA_OPT -jar $APP_NAME >$LOG_PATH 2>&1 & # 應(yīng)以非阻塞方式執(zhí)行服務(wù)啟動(dòng)命令,避免腳本一直阻塞在這里無法退出 # 業(yè)務(wù)方應(yīng)對(duì)其服務(wù)啟動(dòng)時(shí)間進(jìn)行預(yù)估,如果從 命令下發(fā)到端口開啟并對(duì)外提供服務(wù) 期間的時(shí)長(zhǎng)超過了1分鐘 # 那么業(yè)務(wù)方則需酌情在此處使用sleep來阻塞腳本,避免因啟動(dòng)時(shí)間過長(zhǎng)導(dǎo)致持續(xù)交付系統(tǒng)誤判 # 這個(gè)阻塞的時(shí)間按照各業(yè)務(wù)方不同服務(wù)自行設(shè)定 # 且執(zhí)行啟動(dòng)命令后,相關(guān)的服務(wù)日志應(yīng)存儲(chǔ)到指定的文件 echo "${SERVER_NAME}服務(wù)進(jìn)程啟動(dòng)成功" fi } # 停止程序 stop() { # 服務(wù)停止方式及具體方法由業(yè)務(wù)方指定,避免因直接kill掉進(jìn)程而影響線上業(yè)務(wù) # 并且確保stop函數(shù)執(zhí)行結(jié)束后,服務(wù)進(jìn)程不存在,避免影響后續(xù)操作 is_exist if [ $? -eq "0" ]; then # 如果服務(wù)需要平穩(wěn)的停止,保證業(yè)務(wù)流無問題,那么可使用不限于循環(huán)等方式,保證stop執(zhí)行后已經(jīng)停止了該服務(wù) # 否則后續(xù)操作可能會(huì)影響相關(guān)的業(yè)務(wù),務(wù)必確保stop函數(shù)執(zhí)行結(jié)果的準(zhǔn)確性 kill -9 $pid echo "${SERVER_NAME}服務(wù)進(jìn)程停止成功,pid=$pid" else echo "${SERVER_NAME}服務(wù)沒有啟動(dòng),請(qǐng)進(jìn)一步驗(yàn)證是否需要停止進(jìn)程" fi } # 程序狀態(tài) status() { is_exist if [ $? -eq "0" ]; then echo "${SERVER_NAME}服務(wù)進(jìn)程正在運(yùn)行,pid=${pid}" else echo "${SERVER_NAME}服務(wù)進(jìn)程沒有啟動(dòng)" fi } # 重啟程序 restart() { stop start } # 主方法入口,接收參數(shù)可支持start\stop\status\restart\ case "$1" in "start") start ;; "stop") stop ;; "status") status ;; "restart") restart ;; *) usage ;; esac
創(chuàng)建 start.bat 文件,這個(gè)是 Windows 環(huán)境下的啟動(dòng)腳本,具體內(nèi)容如下。
echo off :: 項(xiàng)目名稱 set APP_NAME=${project.artifactId} :: jar名稱 set APP_JAR=${project.build.finalName}.jar echo "開始啟動(dòng)服務(wù) %APP_NAME%" java -Xms512m -Xmx512m -server -jar ../boot/%APP_JAR% echo "java -Xms512m -Xmx512m -server -jar ../boot/%APP_JAR%" goto end :end pause
3.3、創(chuàng)建打包配置文件
最后,我們?cè)?assembly 文件夾下創(chuàng)建一個(gè) assembly.xml 配置文件,具體內(nèi)容如下。
<assembly> <!-- 必須寫,否則打包時(shí)會(huì)有 assembly ID must be present and non-empty 錯(cuò)誤 這個(gè)名字最終會(huì)追加到打包的名字的末尾,如項(xiàng)目的名字為 test-0.0.1-SNAPSHOT, 則最終生成的包名為 test-0.0.1-SNAPSHOT-assembly.tar.gz --> <id>assembly</id> <!-- 打包的類型,如果有N個(gè),將會(huì)打N個(gè)類型的包 --> <formats> <format>tar.gz</format> <!--<format>zip</format>--> </formats> <includeBaseDirectory>true</includeBaseDirectory> <!--第三方依賴設(shè)置--> <dependencySets> <dependencySet> <!-- 不使用項(xiàng)目的artifact,第三方j(luò)ar不要解壓,打包進(jìn)zip文件的lib目錄 --> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>lib</outputDirectory> <unpack>false</unpack> </dependencySet> </dependencySets> <!--文件設(shè)置--> <fileSets> <!-- 0755->即用戶具有讀/寫/執(zhí)行權(quán)限,組用戶和其它用戶具有讀寫權(quán)限; 0644->即用戶具有讀寫權(quán)限,組用戶和其它用戶具有只讀權(quán)限; --> <!-- 將src/main/assembly/bin目錄下的所有文件輸出到打包后的bin目錄中 --> <fileSet> <directory>${basedir}/src/main/assembly/bin</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> <!--如果是腳本,一定要改為unix.如果是在windows上面編碼,會(huì)出現(xiàn)dos編寫問題--> <lineEnding>unix</lineEnding> <filtered>true</filtered><!-- 是否進(jìn)行屬性替換 --> </fileSet> <!-- 將src/main/resources下配置文件打包到config目錄 --> <fileSet> <directory>${basedir}/src/main/resources</directory> <outputDirectory>/config</outputDirectory> <includes> <include>*.xml</include> <include>*.properties</include> <include>*.yml</include> </includes> <filtered>true</filtered><!-- 是否進(jìn)行屬性替換 --> </fileSet> <!-- 將第三方依賴打包到lib目錄中 --> <fileSet> <directory>${basedir}/target/lib</directory> <outputDirectory>lib</outputDirectory> <fileMode>0755</fileMode> <includes> <include>*.jar</include> </includes> </fileSet> <!-- 將項(xiàng)目啟動(dòng)jar打包到boot目錄中 --> <fileSet> <directory>${basedir}/target</directory> <outputDirectory>boot</outputDirectory> <includes> <include>${project.build.finalName}.jar</include> </includes> </fileSet> </fileSets> </assembly>
3.4、打包測(cè)試
配置修改完畢后,我們對(duì)項(xiàng)目進(jìn)行打包。將生成的壓縮包解壓后可以發(fā)現(xiàn),boot 文件夾下項(xiàng)目 jar 包和lib文件夾下第三方 jar 分開了,并且項(xiàng)目 jar 體積也十分小巧。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Java程序并發(fā)的Wait-Notify機(jī)制
這篇文章主要介紹了詳解Java程序并發(fā)的Wait-Notify機(jī)制,多線程并發(fā)是Java編程中的重要部分,需要的朋友可以參考下2015-07-07Java?離線中文語音文字識(shí)別功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java?離線中文語音文字識(shí)別,本次使用springboot?+maven實(shí)現(xiàn),官方demo為springboot+gradle,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07前端dist包放到后端springboot項(xiàng)目下一起打包圖文教程
這篇文章主要介紹了前端dist包放到后端springboot項(xiàng)目下一起打包的相關(guān)資料,具體步驟包括前端打包、將前端文件復(fù)制到后端項(xiàng)目的static目錄、后端打包、驗(yàn)證部署成功等,需要的朋友可以參考下2025-01-01spring6+JDK17實(shí)現(xiàn)SSM起步配置文件
本文介紹了使用Spring6和JDK17配置SSM(Spring + Spring MVC + MyBatis)框架,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01IDEA運(yùn)行spring項(xiàng)目時(shí),控制臺(tái)未出現(xiàn)的解決方案
文章總結(jié)了在使用IDEA運(yùn)行代碼時(shí),控制臺(tái)未出現(xiàn)的問題和解決方案,問題可能是由于點(diǎn)擊圖標(biāo)或重啟IDEA后控制臺(tái)仍未顯示,解決方案提供了解決方法,包括通過右三角Run運(yùn)行(快捷鍵:Alt+42)或以Debug運(yùn)行(快捷鍵:Alt+5)來解決2025-01-01Java Thread多線程開發(fā)中Object類詳細(xì)講解
這篇文章主要介紹了Java Thread多線程開發(fā)中Object類,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-03-03