靈活使用Maven Profile

程式猿講故事發表於2019-07-30

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

相關文章