SpringBoot 打包部署最佳實(shí)踐
spring boot介紹
Spring Boot目前流行的java web應(yīng)用開發(fā)框架,相比傳統(tǒng)的spring開發(fā),spring boot極大簡化了配置,并且遵守約定優(yōu)于配置的原則即使0配置也能正常運(yùn)行,這在spring中是難以想象的。spring boot應(yīng)用程序可以獨(dú)立運(yùn)行,框架內(nèi)嵌web容器,使得web應(yīng)用程序可以像本地程序一樣啟動和調(diào)試,十分的方便,這種設(shè)計(jì)方式也使得spring boot應(yīng)用程序非常適合容器化進(jìn)行大規(guī)模部署。生態(tài)方面,spring boot提供了非常豐富的組件,目前流行的java web框架基本都有spring boot版本,生態(tài)十分龐大,是目前java web開發(fā)最好的方案。
Springboot應(yīng)用程序有兩種運(yùn)行方式
以jar包方式運(yùn)行
以war包方式運(yùn)行
兩種方式應(yīng)用場景不一樣,各有優(yōu)缺點(diǎn)
通過插件spring-boot-maven-plugin,在進(jìn)行打包時,會動態(tài)生成jar的啟動類org.springframework.boot.loader.JarLauncher,借助該類對springboot應(yīng)用程序進(jìn)行啟動。
本地?zé)o需搭建web容器,方便開發(fā)和調(diào)試。
因?yàn)樽詭eb容器,可以避免由于web容器的差異造成不同環(huán)境結(jié)果不一致問題。
一個jar包就是全部,方便應(yīng)用擴(kuò)展。
借助容器化,可以進(jìn)行大規(guī)模的部署。
應(yīng)用過于獨(dú)立,難以統(tǒng)一管理。
數(shù)據(jù)源無法通過界面進(jìn)行管理。
應(yīng)用體積過大。
修改web容器相關(guān)配置較為困難,需要借助代碼實(shí)現(xiàn)。
以war包方式運(yùn)行,通過maven插件spring-boot-maven-plugin進(jìn)行相關(guān)配置后,最終生成一個可運(yùn)行在tomcat,weblogic等java web容器中的war包。
可以借助web容器管理界面對應(yīng)用進(jìn)行管理。
可以管理JNDI數(shù)據(jù)源。
web容器配置較為靈活,配置和程序分離。
應(yīng)用體積較小,甚至可以借助web容器的包管理功能(比如weblogic Library)進(jìn)一步減小應(yīng)用大小。
本地需要搭建web容器,對本地環(huán)境要求更高點(diǎn),學(xué)習(xí)成本也響應(yīng)更高。
調(diào)試較為困難,需要借助web容器。
無法兼容所有web容器(比如spring boot2.x無法運(yùn)行在weblogic 11g上)。
在實(shí)際的項(xiàng)目中,并沒有哪一種方式是最好的,根據(jù)客戶不同的需求制定不同的部署方案,比如有些客戶比較看中管理功能,要求數(shù)據(jù)源和tomcat相關(guān)配置必須由管理員進(jìn)行管理,那么選擇war包方式,有些客戶希望借助容器化進(jìn)行大規(guī)模部署,那么jar方式更適合。不管選擇哪種方式,在部署時都會遇到下面的問題
如果需要打war包,那么不僅是pom文件需要修改,應(yīng)用程序也要做相應(yīng)的改動,改動完后,應(yīng)用程序就無法本地運(yùn)行,需要打完包后將配置信息修改回來,這樣不僅麻煩,還容易出錯。
不管是war包還是jar包,如何管理不同環(huán)境的配置文件,保證不會出錯,雖然spring boot有提供spring.profiles.active配置設(shè)置不同的環(huán)境,但一方面需要人為修改配置文件,只要是人為的就有可能出錯,另一方面,客戶有時出于安全考慮不會提供生產(chǎn)環(huán)境配置信息,那么這時候就無法指定prifiles.active。
如何將多個spring boot模塊打包在一起。
jar包需要配合容器化才能發(fā)揮出最大的優(yōu)勢,如果沒有容器,spring boot jar包就是一個玩具,隨處運(yùn)行的jar包,缺少統(tǒng)一管理,是達(dá)不到生產(chǎn)的要求,那么如果從jar包到容器也是一個問題。
早期碰到這些問題,都是人工解決,不僅效率十分低下,部署一次都需要十幾分鐘,而且很容易出錯,一百次出錯一次算是概率低了,但是生產(chǎn)出錯一次都是重大事件,所以我們也在思考如何通過自動化解決以上問題,如何將開發(fā)和部署分離,開發(fā)人員只關(guān)心開發(fā),開發(fā)完提交代碼,打包和部署都是后臺透明的完成。以下就是我們的解決方案。
spring boot打war包的步驟如下
在pom.xml中將打包方式改為war。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">...war...設(shè)置spring-boot-starter-tomcat范圍為providedorg.springframework.bootspring-boot-starter-tomcatprovided修改spring boot的啟動類,繼承SpringBootServletInitializerpublic class DemoApplication extends SpringBootServletInitializer{@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(DemoApplication.class);}}
每打包一次都要修改pom.xml和啟動類,打包完再修改回來,十分的繁瑣,因?yàn)?,我們提出以下整改方?/span>
從pom.xml復(fù)制一個pom-war.xml文件,將pom-war.xml修改為war包配置
在根目錄下(除了src目錄外都可以)復(fù)制一份啟動類的代碼,修改為war包的配置方式。
編寫shell腳本進(jìn)行打包。
備份當(dāng)前啟動類的java代碼。 將war包啟動類的代碼替換掉當(dāng)前啟動類的代碼。 maven指定pom-war.xml文件進(jìn)行打包。 打包結(jié)束后恢復(fù)啟動類文件。
app-war.sh#!/usr/bin/env bashv1=src/main/java/com/definesys/demo/DemoApplication.javav2=war/DemoApplication.javav3=war/DemoApplication-bak.javacp -rf $v2 $v1mvn clean package -Dmaven.test.skip=true -f war-pom.xml#recoverycp -rf $v3 $v1
通過預(yù)先配置好pom文件和啟動類文件,開發(fā)人員只要運(yùn)行app-war.sh腳本無需修改任何文件即可生成war包。
以上方案pom文件和啟動類文件都需要預(yù)先準(zhǔn)備好,未實(shí)現(xiàn)完全的自動化,通過優(yōu)化方案做到完全自動化。
腳本可以通過find命令搜索以*Application.java結(jié)尾的文件,作為啟動類文件,讀取文件名獲取類名,通過字符串替換方式動態(tài)生成war包啟動類文件。
在pom.xml中用注釋設(shè)置好錨點(diǎn),腳本通過替換錨點(diǎn)動態(tài)生成pom.xml文件。
如果不希望通過錨點(diǎn)實(shí)現(xiàn),可以借助更高級的腳本語言,比如python對xml進(jìn)行解析,再動態(tài)生成xml。
這里的多模塊指的是maven中的多模塊,項(xiàng)目工程中的代碼多模塊,一個項(xiàng)目按功能劃分模塊后,在創(chuàng)建工程時一般也按照功能層面上的模塊進(jìn)行創(chuàng)建,這樣避免一個模塊代碼過于龐大,也利于任務(wù)的分工,但打包卻更麻煩了。
每個模塊都是獨(dú)立的spring boot程序,整合到一個包的時候會出現(xiàn)多個啟動類,多個配置文件沖突的問題。
每個模塊有引用相同的依賴,依賴包版本升級后,需要每個pom文件都做修改。
通過優(yōu)化項(xiàng)目結(jié)構(gòu)解決以上問題
父項(xiàng)目的pom指定spring boot的依賴和公共的依賴。
創(chuàng)建一個spring boot的子項(xiàng)目,作為啟動項(xiàng)目,我們稱為start項(xiàng)目。
其余子項(xiàng)目為普通的java maven項(xiàng)目,parent設(shè)置為第一步創(chuàng)建的spring boot父項(xiàng)目。
start項(xiàng)目的pom引用其他子項(xiàng)目的依賴。
本地調(diào)試可以直接運(yùn)行start的啟動類,ide會自動編譯其他模塊并引用。
打包可以在父項(xiàng)目上進(jìn)行install后再進(jìn)入start項(xiàng)目進(jìn)行打包,腳
mvn clean installcd startmvn clean package
.├── pom.xml├── role│ ├── pom.xml│ └── src│ ├── main│ │ ├── java│ │ │ └── com│ │ │ └── definesys│ │ │ └── demo│ │ │ └── controller│ │ │ └── RoleController.java│ │ └── resources├── start│ ├── pom.xml│ ├── src│ │ ├── main│ │ │ ├── java│ │ │ │ └── com│ │ │ │ └── definesys│ │ │ │ └── demo│ │ │ │ └── DemoApplication.java│ │ │ └── resources│ │ │ └── application.properties└── user├── pom.xml└── src└── main├── java│ └── com│ └── definesys│ └── demo│ └── controller│ └── UserController.java└── resources
start pom.xml<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">blog0915com.definesys.demo1.0-SNAPSHOT4.0.0startcom.definesys.demouser1.0.0com.definesys.demorole1.0.0org.springframework.bootspring-boot-maven-plugin
父項(xiàng)目parent為spring boot,引用spring boot相關(guān)依賴和各個子項(xiàng)目公共的依賴
父項(xiàng)目 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">4.0.0pomuserrolestartorg.springframework.bootspring-boot-starter-parent2.1.2.RELEASEcom.definesys.demoblog09151.0-SNAPSHOTorg.springframework.bootspring-boot-starter-web
所有非start的子項(xiàng)目需要指定版本號并且父項(xiàng)目都設(shè)為根目錄項(xiàng)目。
子項(xiàng)目 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> blog0915 com.definesys.demo 1.0-SNAPSHOT 4.0.0 role 1.0.0所有子項(xiàng)目的包路徑前綴必須一樣,并且以start項(xiàng)目作為基本路徑。
spring boot提供spring.profiles.active指定配置文件,但生產(chǎn)環(huán)境有時候客戶出于安全考慮不提供配置信息給開發(fā)人員,而是預(yù)先將配置文件上傳到服務(wù)器指定路徑,程序需要在運(yùn)行時去引用該配置文件,如果運(yùn)行環(huán)境是kubernetes,則會提供一個config map作為配置文件,這時候就要求spring boot程序讀取外部配置文件。
這里討論的是線上環(huán)境配置文件方案,本地調(diào)試參考子模塊打包相關(guān)內(nèi)容,可以將配置文件統(tǒng)一寫在start項(xiàng)目中。
jar包外部配置文件讀取
jar運(yùn)行可以通過指定參數(shù)spring.config.location引用外部文件,命令參考如下:
java -jar start-1.0-SNAPSHOT.jar --spring.config.location=/Users/asan/workspace/config
config目錄存放properties配置文件
可以通過配合spring.profiles.active參數(shù)可以指定目錄下配置文件,如:
java -jar start-1.0-SNAPSHOT.jar --spring.profiles.active=prod --spring.config.location=/Users/asan/workspace/config
則會讀取/Users/asan/workspace/config/appliction-prod.properties文件作為配置文件。
war包外部配置文件讀取
以tomcat為例,需要在tomcat啟動時指定-Dspring.config.location參數(shù),可以設(shè)置服務(wù)器環(huán)境變量CATALINA_OPTS達(dá)到目的??梢跃庉嬘脩?prifile文件
export CATALINA_OPTS=/Users/asan/workspace/config
同樣,也可以通過-Dspring.profiles.active指定配置文件名稱。
spring boot借助容器化,可以如虎添翼,發(fā)揮出更大的威力,也只有通過容器化,才能體會到spring boot開發(fā)的高效。通過以上的介紹,你可以很順利的打好一個jar包或者war包,那么可以通過編寫dockerfile文件進(jìn)行鏡像的構(gòu)建。spring boot在構(gòu)建鏡像時有兩個地方需要考慮時區(qū)問題,基礎(chǔ)鏡像的時區(qū)默認(rèn)是UTC,比北京時間早8小時,需要指定鏡像時區(qū)。
配置文件問題,需要指定外部配置文件(根據(jù)項(xiàng)目具體情況選擇)。
app-jar-dockerfile.DockerfileFROM openjdk:8-jdk-alpineMAINTAINER definesys.comVOLUME /tmpADD start-1.0-SNAPSHOT.jar app.jarRUN echo "Asia/Shanghai" > /etc/timezoneEXPOSE 8080ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","--spring.config.location=/Users/asan/workspace/config","/app.jar"]app-war.dockerfile.DockerfileFROM tomcatMAINTAINER definesys.comENV CATALINA_OPTS -Dspring.config.location=file:/middleware/config/ADD start-1.0-SNAPSHOT.war /usr/local/tomcat/webapps/app.jarRUN echo "Asia/Shanghai" > /etc/timezoneEXPOSE 8080
早期我們采用的是以下部署過程
逆鋒起筆是一個專注于程序員圈子的技術(shù)平臺,你可以收獲最新技術(shù)動態(tài)、最新內(nèi)測資格、BAT等大廠的經(jīng)驗(yàn)、精品學(xué)習(xí)資料、職業(yè)路線、副業(yè)思維,微信搜索逆鋒起筆關(guān)注!

