項目中一直應用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。當定義了相同名稱的屬性時,很可能會導致意外的結果。