專案中一直應用Maven的profile特性解決不同環境的部署問題。最近在嘗試解決本地除錯環境的時候碰到一些問題,順便仔細研究了一下。因為專案仍然在用普通SpringMVC架構,沒有切換到Spring Boot,所以例子以SpringMVC為基礎。
這裡就不介紹Profile的基礎知識了,不瞭解的請找相關資料查一下。
1 Profile的基礎使用
我們常見的兩種使用Profile的方法:佔位符替換和檔案複製。
1.1 Profile定義
在專案的pom.xml中定義不同的profile,以資料庫主機地址為例。
<profiles> <profile> <id>dev</id> <properties> <active.profile>dev</active.profile> <database.host>localhost</database.host> </properties> </profile> <profile> <id>test</id> <properties> <active.profile>test</active.profile> <database.host>test.codestory.tech</database.host> </properties> </profile> <profile> <id>prod</id> <properties> <active.profile>prod</active.profile> <database.host>prod.codestory.tech</database.host> </properties> </profile> </profiles> |
1.2 替換佔位符方法
為了簡化,將本位涉及的所有引數儲存到 src/main/resources/config下的props.properties 檔案中,格式為
database.pool.host=${database.host} |
在pom.xml中定義 resources 外掛,定製資源複製的動作。
<build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering><!-- 替換佔位符 --> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> <configuration> <encoding>UTF-8</encoding> <overwrite>true</overwrite><!-- 目標檔案存在時覆蓋 --> </configuration> </plugin> </plugins> </build> |
執行 maven 命令,指定 profile 複製資源,複製的資源在目錄 target/classes 下。分別用三個不同的profile執行mvn 命令後結果如下:
mvn clean resources:resources -P dev
database.pool.host=localhost |
mvn clean resources:resources -P test
database.pool.host=test.codestory.tech |
mvn clean resources:resources -P prod
database.pool.host=prod.codestory.tech |
1.3 複製檔案方法
除了使用properties替換佔位符的方法,還可以分別為每個profile編寫檔案,打包時根據選擇的profile進行復制。
建立各個profile需要的配置檔案,在src/main/resources 中建立目錄 profiles ,並在其中建立三個子目錄:dev/test/prod,每個子目錄中建立一個props.properties檔案,內容分別為
src/main/resources/profiles/dev/props.properties
database.pool.host=localhost |
src/main/resources/profiles/test/props.properties
database.pool.host=test.codestory.tech |
src/main/resources/profiles/prod/props.properties
database.pool.host=prod.codestory.tech |
為了測試resources-plugin的引數 overwrite ,我們將 src/main/resources/config/props.properties 內容增加一行,變為
database.pool.host=${database.host} database.pool.port=3306 |
在pom.xml中修改resources部分配置
<resources> <resource> <directory>src/main/resources</directory> <excludes> <exclude>profiles/**</exclude> </excludes> <filtering>true</filtering><!-- 替換佔位符 --> </resource> <resource> <directory>src/main/resources/profiles/${active.profile}</directory> <targetPath>config</targetPath> <filtering>false</filtering><!-- 不替換佔位符,直接複製 --> </resource> </resources> |
同樣執行maven resources命令後檢視檔案內容,
mvn clean resources:resources -P dev
database.pool.host=localhost |
注意屬性檔案中沒有 database.pool.port=3306 這一行,說明是複製檔案的結果,而不是直接替換佔位符。
2 同時使用多個profile
前面的例子足夠簡單,也能解決大部分場景下打包的問題。擴充套件一下場景,看看問題如何解決?
2.1 本地用test環境除錯
為了場景需要,假設props.properties檔案中還有一個引數,用於記錄附件的儲存路徑(為了場景假設的,使用分散式檔案伺服器或webdav等技術的同學請忽視)。
database.pool.host=${database.host} filesystem.path.root=${path.root} |
現在測試同學在測試環境發現了BUG,開發需要訪問test環境資料庫進行聯調,但附件儲存路徑不同,本地不能直接使用 -P test。使用Tomcat遠端除錯的同學也請繞道一下。另外還有一個簡單的辦法,修改一下pom.xml中的profile[test]中path.root引數即可解決。不過為了研究profile,也不用這個太簡單的方案。
2.2 多個profile 替換佔位符的方法
解決的思路是保持原有的profile配置資訊不變,額外選中一個本地除錯用的profile,替換其中少量引數。
pom.xml中profiles內容修改為
<profiles> <profile> <id>local</id> <properties> <active.profile>local</active.profile> <path.root>d:/develop/attachments</path.root> </properties> </profile> <profile> <id>dev</id> <properties> <active.profile>dev</active.profile> <database.host>localhost</database.host> <path.root>d:/develop/attachments</path.root> </properties> </profile> <profile> <id>test</id> <properties> <active.profile>test</active.profile> <database.host>test.codestory.tech</database.host> <path.root>/app/attachments</path.root> </properties> </profile> <profile> <id>prod</id> <properties> <active.profile>prod</active.profile> <database.host>prod.codestory.tech</database.host> <path.root>/app/attachments</path.root> </properties> </profile> </profiles> |
使用多個profile,在-P引數後,只需要用逗號分隔即可。我的目的是用local中的引數替換test中同名引數,所以將 local放在後面。(需要在pom.xml中註釋掉<directory>src/main/resources/profiles/${active.profile}</directory>這個resource定義)
mvn clean resources:resources -P test,local
database.pool.host=test.codestory.tech filesystem.path.root=/app/attachments |
發現檔案內容並沒有按照我預期的目標替換,而是仍然用了test的引數。在網上搜尋,在百度知道一個回答中找到了答案 https://zhidao.baidu.com/question/139071460381210925.html ,【它是根據profile定義的先後順序來進行覆蓋取值的,然後後面定義的會覆蓋前面定義的。】
因此,修改 pom.xml中profiles的順序,將local放到最後,重新執行命令
mvn clean resources:resources -P test,local
database.pool.host=test.codestory.tech filesystem.path.root= d:/develop/attachments |
2.3 多個profile複製檔案
再來試試複製檔案的方法是否繼續有效。為了測試方便,在profiles/{active.profile}的目錄下,分別放置了一個不同的屬性檔案,檔名含profile名,分別為env-dev.properties/env-test.properties /env-prod.properties。
首先,只用一個profile測試
mvn clean resources:resources -P test
在target/classes/config 目錄中可以看到兩個檔案 env-test.properties和props.properties,說明覆制檔案成功;檢視檔案內容,可以發現都是從 src/main/resources/profiles/test 目錄複製而來。
測試兩個profile,再檢查目錄 target/classes/config,發現只有一個檔案 props.properties,並且內容是 src/main/resource/config/props.properties檔案替換佔位符的結果。
mvn clean resources:resources -P test,local
database.pool.host=test.codestory.tech filesystem.path.root=d:/develop/attachments |
為了測試原因,在 src/main/resource/config/props.properties 中增加一個引數activeProfiles,檔案內容為:
database.pool.host=${database.host} filesystem.path.root=${path.root} active.profiles=${active.profile} |
mvn clean resources:resources -P test,local
database.pool.host=test.codestory.tech filesystem.path.root=d:/develop/attachments active.profiles=local |
原因在於:根據優先順序,引數active.profile只保留了最後一個 local,所以無法實現拷貝 test 目錄下檔案的效果。
2.4 修改profile複製檔案方法
在maven的pom規範中,在每個profile中還可以定義build引數,因此將pom.xml中profiles部分內容修改為
<profiles> <profile> <id>dev</id> <properties> <active.profile>dev</active.profile> <database.host>localhost</database.host> <path.root>d:/develop/attachments</path.root> </properties> <build> <resources> <resource> <directory>src/main/resources/profiles/dev</directory> <targetPath>config</targetPath> <filtering>false</filtering> </resource> </resources> </build> </profile> <profile> <id>test</id> <properties> <active.profile>test</active.profile> <database.host>test.codestory.tech</database.host> <path.root>/app/attachments</path.root> </properties> <build> <resources> <resource> <directory>src/main/resources/profiles/test</directory> <targetPath>config</targetPath> <filtering>false</filtering> </resource> </resources> </build> </profile> <profile> <id>prod</id> <properties> <active.profile>prod</active.profile> <database.host>prod.codestory.tech</database.host> <path.root>/app/attachments</path.root> </properties> <build> <resources> <resource> <directory>src/main/resources/profiles/prod</directory> <targetPath>config</targetPath> <filtering>false</filtering> </resource> </resources> </build> </profile> <profile> <id>local</id> <properties> <active.profile>local</active.profile> <path.root>d:/develop/attachments</path.root> </properties> </profile> </profiles> |
可以看到,在每個profile中增加了檔案複製的內容。同之前配置的區別在於:不再使用變數 ${active.profile},而是直接寫profile的名稱。刪除之前定義的<directory>src/main/resources/profiles/${active.profile}</directory>,再次測試
mvn clean resources:resources -P test,local
在target/classes/config 目錄中可以看到兩個檔案 env-test.properties和props.properties,說明覆制檔案成功。
當然這時候想達到本節開始的場景:本地使用test資料庫除錯,需要拆分props.properties為兩個檔案,分別處理了:資料庫資訊放一個檔案(使用複製檔案的方法),檔案目錄放另一個檔案(使用替換佔位符的方法)。
3 嘗試在專案配置檔案中記錄所使用的Profiles
前面的例子中,使用active.profiles=${active.profile}記錄的值,只有最後一個profile的id。如果想記錄所有使用到的profile,希望配置檔案中的值是active.profiles=test,local。該怎麼做呢?
經過測試,發現maven有一個內建引數是 activeProfiles。將原始配置檔案修改為 active.profiles=${activeProfiles}
mvn clean resources:resources -P test,local
active.profiles=[Profile {id: test, source: pom}, Profile {id: local, source: pom}] |
在網上搜尋了很久,沒發現用什麼辦法能夠處理${activeProfiles}的輸出值。不過文字也足夠簡單,可以在專案中讀出這個字串後進行後續處理,比如處理為: active.profiles=test,local
4 在Maven的settings.xml中定義profile
除了專案pom.xml中定義profile,還可以在maven/conf/settings.xml中定義。為了測試profile的優先順序,定義了兩個profile,並且新加了一個屬性active.profile.label,並且將local和test的順序互換。
<profiles> <profile> <id>local</id> <properties> <active.profile>local</active.profile> <active.profile.label>settings profile local</active.profile.label> <filesystem.path.root>d:/develop/attachments</filesystem.path.root> </properties> </profile> <profile> <id>test</id> <properties> <active.profile>test</active.profile> <active.profile.label>settings profile test</active.profile.label> </properties> </profile> </profiles> |
建立一個profiles.txt檔案用於輸出,原始內容(為了區別輸出內容,增加了#字元分隔行)
############################################### active.profiles=${activeProfiles} ############################################### active.profile.label=${active.profile.label} ############################################### |
使用命令
mvn clean resources:resources -P test,local
############################################### active.profiles=[Profile {id: test, source: pom}, Profile {id: local, source: pom}, Profile {id: local, source: settings.xml}, Profile {id: test, source: settings.xml}] ############################################### active.profile.label=settings profile test ############################################### |
由此可見,當同時在pom.xml和settins.xml中定義了相同id的profile,其載入順序是先依次載入 pom.xml中的Profiles,再載入settings.xml中的profiles。當定義了相同名稱的屬性時,很可能會導致意外的結果。