一個小技巧,Maven的打Jar包體積減少100倍
大家好,我是一航!
為了大家能夠從從專案整合的苦力活中解放出來,最近這一週,整理了兩篇關於Jenkins構建、部署的文章,感興趣的朋友可以看看:
還在手動發包?手把手教你 Jenkins 自動化部署SpringBoot
這期間,和粉絲大佬交流到關於Maven構建的最佳化、壓縮的問題;一段簡單的配置,就可以將包Jar包的大小縮小近百倍,實際的開發部署過程中,非常的實用的一個小技巧,在這裡分享給各位;
SpringBoot專案的依賴,我們一般都會採用Maven管理,整個專案,一般都分為以下幾部分:
-
三方依賴
透過pom.xml檔案配置,新增到專案中來
特點: 變化小,佔用空間大
-
業務程式碼
特點: 變化大,佔用空間小
-
靜態資源
特點:變化適中,佔用空間大;不過一般的靜態資源都另外管理,很少會直接放在專案裡面;
而整個專案通常會被構建成一個Jar,上傳到伺服器執行;整個Jar包中,三方依賴會被一併打包進去,佔用空間最大的,也就是這部分依賴包;
比如下面這個最基本的測試 SpringBoot 專案,就一個簡單的hello world介面,但是打包出來的jar就有
20多M
;
把Jar包解壓之後,發現三方依賴竟然比整個Jar包都大(可能壓縮的原因),自己的程式碼只有100多K;
這還只是一個最基礎的專案,如果業務複雜,依賴多,光是三方包可能就佔用幾十、幾百M之多;
由於依賴包 變化小,佔用空間大的特點,大部分情況是第一次新增之後,後面很少會去調整;但每次修改哪怕是一行程式碼,都需要重新把他們構建Jar中去,往伺服器上傳、釋出,白白消耗了大量的資源、頻寬以及時間;
那能否將三方依賴和自己的業務程式碼分開打包呢?答案是: 可以的;
1 依賴拆分配置
只需要在專案
pom.xml
檔案中新增下面的配置:
<
build>
<
plugins>
<
plugin>
<
groupId>org.apache.maven.plugins
</
groupId>
<
artifactId>maven-compiler-plugin
</
artifactId>
<
configuration>
<
source>1.8
</
source>
<
target>1.8
</
target>
</
configuration>
</
plugin>
<
plugin>
<
groupId>org.springframework.boot
</
groupId>
<
artifactId>spring-boot-maven-plugin
</
artifactId>
<
configuration>
<
fork>true
</
fork>
<
finalName>${project.build.finalName}
</
finalName>
<!--解決windows命令列視窗中文亂碼-->
<
jvmArguments>-Dfile.encoding=UTF-8
</
jvmArguments>
<
layout>ZIP
</
layout>
<
includes>
<!--這裡是填寫需要包含進去的jar,
必須專案中的某些模組,會經常變動,那麼就應該將其座標寫進來
如果沒有則non-exists ,表示不打包依賴
-->
<
include>
<
groupId>non-exists
</
groupId>
<
artifactId>non-exists
</
artifactId>
</
include>
</
includes>
</
configuration>
<
executions>
<
execution>
<
goals>
<
goal>repackage
</
goal>
</
goals>
</
execution>
</
executions>
</
plugin>
<!--此外掛用於將依賴包抽出-->
<
plugin>
<
groupId>org.apache.maven.plugins
</
groupId>
<
artifactId>maven-dependency-plugin
</
artifactId>
<
executions>
<
execution>
<
id>copy-dependencies
</
id>
<
phase>package
</
phase>
<
goals>
<
goal>copy-dependencies
</
goal>
</
goals>
<
configuration>
<
outputDirectory>${project.build.directory}/lib
</
outputDirectory>
<
excludeTransitive>false
</
excludeTransitive>
<
stripVersion>false
</
stripVersion>
<
includeScope>runtime
</
includeScope>
</
configuration>
</
execution>
</
executions>
</
plugin>
<
plugin>
<
artifactId>maven-surefire-plugin
</
artifactId>
<
configuration>
<
skip>true
</
skip>
</
configuration>
</
plugin>
</
plugins>
</
build>
再次構建
mvn clean package -DskipTests=true
發現
target
目錄中多了個
lib
資料夾,裡面儲存了所有的
依賴jar
,自己業務相關的jar也只有小小的
157kb
,相比之前21M,
足足小了100多倍;
這種方式打的包,在專案啟動時,需要透過
-Dloader.path
指定
lib
的路徑:
java -Dloader.path=./lib -jar xxx.jar
雖然這樣打包,三方依賴的大小並沒有任何的改變,但有個很大的不同就是我們自己的業務包和依賴包分開了;在不改變依賴的情況下,也就只需要第一次上傳
lib
目錄到伺服器,後續業務的調整、bug修復,在沒調整依賴的情況下,就只需要上傳更新小小的業務包即可;既省資源又省時間;就算是依賴變化了,也只需要更新調整的依賴,沒變的依賴包我們也就不管了。
有朋友可能會說: 你這業務包確實小了,但是無形中增加了對依賴包的管理,提高了管理成本。
沒錯,這種方式,確實增加的了Jar包的管理成本,多人協調開發,構建的時候,還需要專門去關注是否有人更新依賴;
不過這並不是啥大事兒,前面學習的Jenkins自動化工具,就能自動幫我們維護這個lib目錄,減少人工核對,避免維護成本;
配置好之後,你會發現原有大Jar包的上傳/下載;現在變成只有原來的百分之一;整合速度將會有非常明顯的提升;
2 Jenkins 管理依賴拆分的Jar
這一部分的內容依然是對前兩篇關於Jenkins【單模組】、【多模組】打包的完善,透過最佳化指令碼,來實現 Jenkins 對依賴包、業務包的自動增量管理;
所以,這個並不是從0開始的教程,沒看過前兩篇文章的,可以先去掃一眼,然後再繼續往下看;
SSH的方式
SSH的方式相比於之前的方式,只是多了管理lib中jar的過程,未調整的依賴Jar包,不上傳到伺服器;所以相比之前的方案,多了一個檢測指令碼
jenkins_jar_and_lib_check.sh
;他的作用就是在SSH上傳之前,檢測那些依賴更新了,然後只要留已更新的依賴上傳到伺服器;
Jenkins構建的過程
-
拉取最新程式碼
-
Maven打包
-
Jenkins本地執行
jenkins_jar_and_lib_check.sh
檢測依賴Jar和App jar是否更新 -
上傳已經更新的Jar/指令碼
-
遠端執行
jenkins_restart_mini.sh
判斷是否更新並重啟,為了不影響之前的教程,這裡新加了一個指令碼
jenkins_restart_mini.sh
,和前面幾篇文章中提到的jenkins_restart.sh
作用是一樣的-
判斷依賴jar/業務jar是否更新(任意一個更新都需要重啟) -
不需要更新的前提下,判斷程式是否存在 -
重啟服務
-
jenkins_jar_and_lib_check.sh指令碼
Jenkins本地校驗踢出未更新的依賴和業務Jar的指令碼;需要在Maven構建完,ssh傳輸之前使用;
完整指令碼地址:
校驗步驟:
-
模組下建立一個tmp的臨時目錄
-
將業務jar和依賴jar複製的tmp臨時目錄下
-
分別對業務jar和依賴jar進行MD5校驗
-
更新的留下
-
未更新的刪除掉
只有Jenkins本地的校驗才刪除,減少不必要的傳輸;服務端的檢驗不要刪了,每一個都需要使用的;
-
指令碼部分細節說明:
-
Jar包MD5校驗方法:
jar_check_md5()
公共方法!直接透過Jar包的MD5校驗是否更新
# 直接透過jar校驗
jar_check_md5() {
# jar 包的路徑
JAR_FILE=$1
if [ ! -f $JAR_FILE ]; then
# 如果校驗的jar不存在 返回失敗
return 1
fi
JAR_MD5_FILE=${JAR_FILE}.md5
echo "jenkins校驗 JAR的MD5檔案:"$JAR_MD5_FILE
if [ -f $JAR_MD5_FILE ]; then
cat $JAR_MD5_FILE
md5sum $JAR_FILE
md5sum --status -c $JAR_MD5_FILE
RE=$?
md5sum $JAR_FILE > $JAR_MD5_FILE
return $RE
else
md5sum $JAR_FILE > $JAR_MD5_FILE
fi
return 1
} -
Jar 解壓校驗檔案詳情
jar_unzip_check_md5()
公共方法!如果前面直接校驗Jar的方式沒有成功,就需要再透過解壓的方式校驗
# 將Jar解壓之後校驗
jar_unzip_check_md5() {
# jar 包的路徑
UNZIP_JAR_FILE=$1
if [ ! -f $UNZIP_JAR_FILE ]; then
# 如果校驗的jar不存在 返回失敗
return 1
fi
# jar的名稱
UNZIP_JAR_FILE_NAME=`basename -s .jar $UNZIP_JAR_FILE`
echo "jenkins校驗 JAR包名稱:"$UNZIP_JAR_FILE_NAME
# jar所在的路徑
UNZIP_JAR_FILE_BASE_PATH=${UNZIP_JAR_FILE%/${UNZIP_JAR_FILE_NAME}*}
echo "jenkins校驗 JAR包路徑:"$UNZIP_JAR_FILE_BASE_PATH
# 解壓的臨時目錄
JAR_FILE_UNZIP_PATH=${UNZIP_JAR_FILE_BASE_PATH}/jar_unzip_tmp
echo "jenkins校驗 解壓路徑:"$JAR_FILE_UNZIP_PATH
# 用於快取解壓後檔案詳情的目錄
UNZIP_JAR_FILE_LIST=${UNZIP_JAR_FILE_BASE_PATH}/${UNZIP_JAR_FILE_NAME}.files
echo "jenkins校驗 jar檔案詳情路徑:"$UNZIP_JAR_FILE_LIST
# 快取解壓後檔案詳情的MD5
UNZIP_JAR_FILE_LIST_MD5=${UNZIP_JAR_FILE_BASE_PATH}/${UNZIP_JAR_FILE_NAME}.files.md5
echo "jenkins校驗 jar檔案詳情MD5校驗路徑:"$UNZIP_JAR_FILE_LIST
rm -rf $JAR_FILE_UNZIP_PATH
mkdir -p $JAR_FILE_UNZIP_PATH
# 解壓檔案到臨時目錄
unzip $UNZIP_JAR_FILE -d $JAR_FILE_UNZIP_PATH
# 遍歷解壓目錄,計算每個檔案的MD5值及路徑 輸出到詳情列表檔案中
find $JAR_FILE_UNZIP_PATH -type f -print | xargs md5sum > $UNZIP_JAR_FILE_LIST
rm -rf $JAR_FILE_UNZIP_PATH
if [ ! -f $UNZIP_JAR_FILE_LIST_MD5 ]; then
# 如果校驗檔案不存在 直接返回校驗失敗
md5sum $UNZIP_JAR_FILE_LIST > $UNZIP_JAR_FILE_LIST_MD5
return 1
fi
cat $UNZIP_JAR_FILE_LIST_MD5
md5sum $UNZIP_JAR_FILE_LIST
md5sum --status -c $UNZIP_JAR_FILE_LIST_MD5
RE=$?
md5sum $UNZIP_JAR_FILE_LIST > $UNZIP_JAR_FILE_LIST_MD5
# 返回校驗結果
return $RE
} -
彙總判斷
公共方法!這裡彙總了
jar_check_md5
和jar_unzip_check_md5
兩個方法呼叫;注意:由於這是上傳前Jenkins呼叫的本地校驗,一旦校驗發現沒有改變,會執行
rm -f $JAR_FILE
刪除命令;check_md5() {
# jar 包的路徑
JAR_FILE=$1
if [ -f $JAR_FILE ]; then
# 直接透過jar校驗
jar_check_md5 $JAR_FILE
if [ $? = 0 ];then
echo "jenkins校驗 透過Jar的MD5校驗成功"
rm -f $JAR_FILE
return 0
else
echo "jenkins校驗 透過Jar的MD5校驗失敗"
fi
# 透過解壓jar 校驗是否更新
jar_unzip_check_md5 $JAR_FILE
if [ $? = 0 ];then
echo "jenkins校驗 透過解壓的MD5校驗成功"
rm -f $JAR_FILE
return 0
else
echo "jenkins校驗 透過解壓的MD5校驗失敗"
fi
fi
return 1
} -
判斷依賴包
# lib目錄的路徑
MODULE_LIB_PATH=${MODULE_PATH}/target/lib
echo "jenkins校驗 lib目錄:"$MODULE_LIB_PATH
if [ -d $MODULE_LIB_PATH ]; then
# 將打包後的lib下的依賴全部複製到臨時的lib資料夾下
\cp -r ${MODULE_LIB_PATH}/* ${MODULE_TMP_LIB_PATH}
for LIB_JAR_FILE in ${MODULE_TMP_LIB_PATH}/*.jar
do
echo $LIB_JAR_FILE
if [ -f $LIB_JAR_FILE ];then
echo "jenkins校驗依賴Jar:"$LIB_JAR_FILE
check_md5 $LIB_JAR_FILE
if [ $? = 0 ];then
echo "jenkins依賴lib校驗!成功,沒有發生變化"$LIB_JAR_FILE
else
echo "jenkins依賴lib校驗!失敗,已經更新"$LIB_JAR_FILE
fi
fi
done
fi -
判斷業務包
MODULE_JAR=${MODULE_TMP_PATH}/${JAR_NAME}.jar
echo "jenkins校驗專案Jar:"$MODULE_JAR
check_md5 $MODULE_JAR
if [ $? = 0 ];then
echo "jenkins校驗成功,沒有發生變化"
else
echo "jenkins校驗失敗,已經更新"
fi
SSH傳輸說明
前面的教程,SSH傳輸的都是各個專案target目錄下的jar,由於這裡,本地要做校驗,需要快取歷史的MD5值等訊息,就建立了臨時檔案
tmp
,不再使用target(每次編譯都會被清空,無法快取);
因此這裡關於Jenkins的SSH傳輸配置就需要傳輸tmp目錄了,包括下面指令碼中使用的專案路徑,也是tmp目錄,不再使用target了
jenkins_restart_mini.sh
服務端檢測更新,重啟服務的指令碼;
地址:
校驗步驟:
-
遍歷所有模組的tmp目錄 -
校驗依賴Jar、業務Jar是否更新 -
判斷程式是否存在 -
已更新 / 程式不存在;重啟 -
未更新跳過
-
構建測試:
Docker的方式
Docker方式和SSH的方式有比較大的差異,採用SSH的方式,一般是明確知道那些伺服器,然後直接上傳;但採用Docker,最終服務在那些機器上執行,就不一定了,比如使用了K8S;
那就意味著,服務所需的包、依賴,都必須打到Docker映象中,以方便容器啟動時使用;但這似乎又違背了本文的意圖,哪怕是隻有業務更新,也需要把所有的依賴新增到映象中去;
既然業務包也依賴包能拆分,業務(app)映象和依賴(lib)映象分開也就能解決這個問題了,如下圖:
-
基礎依賴映象
避免干擾,每個模組都又自己獨立的依賴映象;只有在依賴變更的情況下構建更新;是否需要推送的映象倉庫
-
業務映象
模組依賴更新/業務更新的時候,重新構建更新
示例專案:
目錄說明:
-
app
儲存業務包以及構建映象用的Dockerfile
-
lib
儲存業務所需的依賴包以及構建依賴映象的Dockerfile
-
docker-image-build.sh
Jenkins 構建依賴映象、業務映象的指令碼
-
docker-image-pull.sh
伺服器獲取最新映象的指令碼
-
docker-compose.yaml
啟動容器的基礎配置檔案
Jenkins構建Docker映象並啟動的過程
-
拉取最新程式碼
-
Maven打包
-
執行
script/jenkins
目錄下的jenkins_docker_build.sh
作用是遍歷所有模組中的
docker-image-build.sh
並逐一執行 -
執行模組下的
docker-image-build.sh
-
校驗依賴jar是否更新
a. 更新構建依賴基礎映象
b. 未更新跳過
-
檢驗業務並構建業務映象
-
將映象推送到映象倉庫
-
-
將
script/jenkins
指令碼以及模組下docker
目錄的指令碼上傳到伺服器 -
執行
jenkins_restart_docker.sh
-
執行 docker-image-pull.sh
下載最新的映象 -
執行 docker-compose.yaml
啟動服務
-
依賴映象的Dockerfile
目錄在/docker/lib/Dockerfile
FROM openjdk:
8
# 同步時區
ENV TZ=Asia/Shanghai
RUN
ln -snf /usr/share/zoneinfo/
$TZ /etc/localtime &&
echo
$TZ > /etc/timezone
# 將lib目錄下的所有jar全部複製到映象裡面
ADD
./*.jar /lib/
就是將所有的目錄包複製到映象中去;
構建映象:
docker build -t lib-jenjins-mini-build:latest ./docker/lib/.
最終會構建出一個名為
lib-jenjins-mini-build:latest
的映象
業務映象的Dockerfile
目錄在/docker/app/Dockerfile
# 整合自依賴基礎映象
FROM lib-jenjins-mini-build:latest
# 將當前目錄下的jar複製到容器類
ADD
./*.jar /app.jar
# 監聽埠
EXPOSE
18092
# 啟動
ENTRYPOINT
[
"java",
"-Dloader.path=/lib",
"-Djava.security.egd=file:/dev/./urandom" \
,
"-XX:+UnlockExperimentalVMOptions",
"-XX:+UseCGroupMemoryLimitForHeap" \
,
"-jar",
"/app.jar" ]
-
FROM
指明基礎映象為前面構建的依賴映象
-
ADD
將業務jar包複製到容器中去
-
EXPOSE 設定監聽埠
-
ENTRYPOINT
啟動服務,這裡務必要指定
-Dloader.path=/lib
,其中/lib
就是前面基礎映象依賴儲存的地址,如果你有調整,這裡也需要跟著調整
docker-image-build.sh
用於構建基礎映象和業務映象的指令碼;
指令碼地址:https://github.com/vehang/ehang-spring-boot/blob/main/spring-boot-012-tools-jenkins-mini-build/docker/docker-image-build.sh
-
公共方法
以下的三個校驗方法屬於公共方法,和前面介紹的一樣
jar_check_md5 透過jar的md5值直接檢測
jar_unzip_check_md5 透過對jar包解壓 校驗檔案詳情的MD5
check_md5 彙總上面兩個方法的校驗
-
準備jar
將業務jar包複製到
docker/app
目錄將依賴Jar複製到
docker/lib
目錄MODULE_LIB_PATH=${MODULE_BATH_PATH}/docker/lib
MODULE_APP_PATH=${MODULE_BATH_PATH}/docker/app
\cp -r ${MODULE_BATH_PATH}/target/*.jar ${MODULE_APP_PATH}
\cp -r ${MODULE_BATH_PATH}/target/lib/*.jar ${MODULE_LIB_PATH} -
校驗並構建依賴映象
檢驗依賴包是否更新,如果更新,構建基礎的依賴映象;
LIB_UPDATE=false
for LIB_JAR_FILE in ${MODULE_LIB_PATH}/*.jar
do
echo $LIB_JAR_FILE
if [ -f $LIB_JAR_FILE ];then
echo "Jenkins Docker映象構建校驗lib 依賴Jar:"$LIB_JAR_FILE
check_md5 $LIB_JAR_FILE
if [ $? = 0 ];then
echo "Jenkins Docker映象構建校驗lib!成功,沒有發生變化"$LIB_JAR_FILE
else
LIB_UPDATE=true
echo "Jenkins Docker映象構建校驗lib!失敗,已經更新"$LIB_JAR_FILE
fi
fi
done
# 一旦發現lib有變化,就構建新的lib映象
if [ $LIB_UPDATE = true ]; then
docker build -t ${MODULE_DOCKER_LIB_IMAGE_NAME}:latest ${MODULE_LIB_PATH}/.
fi -
校驗業務jar並構建映象
校驗業務包、依賴包是否更新
if [ $APP_UPDATE = true ] || [ $LIB_UPDATE = true ]
;更新就重新構建映象;APP_UPDATE=false
for APP_JAR_FILE in ${MODULE_APP_PATH}/*.jar
do
echo $APP_JAR_FILE
if [ -f $APP_JAR_FILE ];then
echo "Jenkins Docker映象構建校驗APP 依賴Jar:"$APP_JAR_FILE
check_md5 $APP_JAR_FILE
if [ $? = 0 ];then
echo "Jenkins Docker映象構建校驗APP!成功,沒有發生變化"$APP_JAR_FILE
else
APP_UPDATE=true
echo "Jenkins Docker映象構建校驗APP!失敗,已經更新"$APP_JAR_FILE
fi
fi
done
# 一旦發現lib有變化,或者APP發生變化 都需要構建新的映象
if [ $APP_UPDATE = true ] || [ $LIB_UPDATE = true ]; then
# 構建映象
docker build -t registry.cn-guangzhou.aliyuncs.com/ehang_jenkins/${MODULE_DOCKER_IMAGE_NAME}:latest ${MODULE_APP_PATH}/.
# 將映象推送到阿里雲
docker push registry.cn-guangzhou.aliyuncs.com/ehang_jenkins/${MODULE_DOCKER_IMAGE_NAME}:latest
fi
構建測試:
再回看一下部署過程,發現曾經最耗時的部分,現在一下變的絲滑好多...
文中有任何的問題或者疑問,歡迎隨時微信(mbb2100)交流;感謝您的點贊、關注!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70035356/viewspace-2996024/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- java打jar包的maven方式JavaJARMaven
- Maven專案打jar包MavenJAR
- iOS App優化1---減少包體積iOSAPP優化
- Webpack + Vue,部署時減少包體積的幾種方法WebVue
- 如何讓雲音樂iOS包體積減少87MBiOS
- IDEA中MAVEN專案打JAR包的簡單方法IdeaMavenJAR
- SQLite重設計:尾部延遲可減少 100 倍SQLite
- 安卓手機減少流量的4個技巧安卓
- Maven把專案依賴的所有jar包都打到同一個jar中MavenJAR
- 減少apk包大小的一種思路APK
- 如何使用Maven將專案中的依賴打進jar包MavenJAR
- 配置Tree Shaking來減少JavaScript的打包體積JavaScript
- Maven引入本地jar包MavenJAR
- 用好這幾個技巧,解決Maven Jar包衝突易如反掌MavenJAR
- 7中方式來減少webpack bundle體積Web
- 多個module實體類集合打一個jar包並上傳至遠端庫JAR
- maven 工程匯入jar包MavenJAR
- 使用Egret外掛壓縮程式碼包體積,減少請求數量的實戰教程
- maven - 引用本地jar,進行jar包移動MavenJAR
- Maven構建引入本地jar包MavenJAR
- 《《《maven倉庫下載jar包MavenJAR
- 配置webpack中externals來減少打包後vendor.js的體積WebJS
- 更改jar包中的一個classJAR
- 10 個最佳化技巧,減少 Docker 映象大小【轉】Docker
- docker容器 如何精簡映象減小體積Docker
- maven用變數的方法統一管理jar包版本Maven變數JAR
- Android開發如何有效減小APK的體積AndroidAPK
- IDEA打可執行的jar包IdeaJAR
- maven專案打包說有依賴jar包到一個資料夾MavenJAR
- 體積減少80%!釋放webpack tree-shaking的真正潛力Web
- 一個小操作,SQL查詢速度翻了1000倍。SQL
- 新增jar包到本地Maven倉庫JARMaven
- Maven 專案引入本地 jar 包方法MavenJAR
- maven專案引進本地jar包MavenJAR
- java-maven生成可執行的 jar 包JavaMavenJAR
- 黑猴子的家:Maven 統一管理目標jar包的版本MavenJAR
- Eclipse打JAR包引用的第三方JAR包找不到的問題。EclipseJAR
- 從Maven專案中獲取Jar包MavenJAR