前言
在 SpringBoot 專案中,我們經常會使用兩種占位符(有時候還會混用),它們分別是:
-
@*@
-
${*}
如果我們上網搜索「SpringBoot 的占位符 @」,大部分答案會告訴你,SpringBoot 的默認占位符由 ${*}變成 @*@了,更好一點的答案會參考 SpringBoot官網 中的描述:
On the last point: since the default config files accept Spring style placeholders (
${…?}) the Maven filtering is changed to use@..@placeholders (you can override that with a Maven propertyresource.delimiter).
于是我們得到了答案,并心安理得地開始使用 @*@占位符,但如果有探索欲比較強的同學問起:Spring 中的占位符本來是 ${*},為啥 SpringBoot 中的占位符就變成 @*@了呢?有時候這兩種占位符還能混用,這又是為什么呢?
今天,我們就來一探究竟,這兩種占位符到底是如何實作的,
場景
首先要說明兩種場景:
-
使用 @Value 注解注入屬性時,只能使用 ${*} 占位符決議,
-
處理資源檔案中的屬性時,這兩種占位符就有點意思了:它們既有可能都有效,還有可能都不生效,甚至你可以擴展自己的占位符!當然這一切都要看你是怎么配置的,下文會進行詳細描述,
我們先簡單看下第一種場景,@Value 注解的處理屬于 Spring 核心框架邏輯,可以參見 PropertySourcesPlaceholderConfigurer 這個類,最侄訓執行 ${*} 占位符的決議,其中的冒號后面可以寫默認值,
由于這種場景不是本文重點,因此不再展開,有興趣的同學可自行探索詳細決議流程,可以參考文章SpringBoot 中 @Value 原始碼決議,
下面我們重點看看第二種場景:處理資源檔案中的屬性占位符,為方便說明,我們搭建一個 Demo 專案,
前置知識
用過 Maven 的同學應該都知道,插件 maven-resources-plugin 就是用來處理資源檔案的,結合前文中提到的 resource.delimite,我們在 spring-boot-starter-parent 中可以找到對應的配置:
可以看到 delimiter 是 maven-resources-plugin 插件中的一個配置項,用于控制占位符的型別,稍后我們會更改其中的一些配置項進行實驗,
專案搭建
我們創建一個 SpringBoot Demo 專案,環境資訊如下:
-
spring-boot 2.6.1
-
maven-resources-plugin 3.2.0
我們需要準備一些配置資料,如下所示:
它們會被 application.properties 參考:
為進行對比,這里我們使用了三種占位符,分別是 Spring 的默認占位符 ${*}、SpringBoot 的默認占位符 @*@,以及我隨便寫的一種占位符 #*#,可以預知的是,默認情況下 #*# 這種占位符一定不會被決議,
然后我們還需要在 pom.xml 進行配置,確保資源被正確決議:
此時 pom.xml 的完整內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>resource.placeholder.demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>resource.placeholder.demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<profiles>
<profile>
<id>product</id>
<properties>
<env>product</env>
</properties>
</profile>
</profiles>
<build>
<filters>
<!-- 指定配置讀取路徑 -->
<filter>src/main/filters/${env}.properties</filter>
</filters>
<resources>
<!-- 把資源檔案中的占位符替換為配置資料 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>static/**</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注:上面我們準備了一個非常簡單的組態檔 product.properties 用于演示,在實際專案中,一般會為不同的 Profile 配置不同的資料,比如除了 product.properties 組態檔外,還可能會有 dev.properties 等等組態檔,
現在,我們 build 一下專案,看看 class 中的資源檔案內容:
很明顯,只有 @*@ 這種占位符被決議了,而 ${*} 和 #*# 都沒有被決議,
那我們修改一下配置(手動引入 maven-resources-plugin,覆寫 parent 中的配置),看看會發生什么:
Reimport Maven 后,再次 build,看看效果:
可以發現把 useDefaultDelimiters 改為 true 后, ${*} 占位符也可以決議了,
那我們繼續改,把 delimite 改成 #,看看 #*# 這種占位符能否被決議:
Reimport Maven 后,再次 build,看看效果:
可以看到,我們自定義的占位符也可以決議了,
繼續實驗,把 useDefaultDelimiters 改回 false:
Reimport Maven 后,再次 build,看看效果:
我們發現,現在只能決議自定義占位符 #*# 了,而 ${*} 和 @*@ 沒有被決議,
基于上面幾項實驗的結果,我們可以大膽推測,maven-resources-plugin 插件的:
-
默認占位符有兩種,分別是 ${*} 和 @*@
-
配置項 useDefaultDelimiters,可以控制是否使用默認占位符
-
配置項 delimiter,既可以寫默認占位符,也可以自定義占位符
好了,現在我們需要到 maven-resources-plugin 插件中找一下對應的原始碼,驗證上述猜測是否正確,
原始碼決議
首先我們要下載 maven-resources-plugin 的原始碼,URL 為https://archive.apache.org/dist/maven/plugins/
在不熟悉原始碼的情況下,我們直接通過關鍵詞 useDefaultDelimiters,定位到關鍵代碼 org.apache.maven.shared.filtering.AbstractMavenFilteringRequest#setDelimiters,打上斷點進行除錯,
PS:可以參考文章 如何除錯 Maven 原始碼和插件原始碼 學習 Maven 插件的除錯方法,具體到本專案,我們可以執行命令 mvnDebug -Pproduct resources:resources 以啟動除錯,其中的 -P 是為了指定 profile,從而能夠找到 ${env}.properties 檔案進行配置資料的讀取,
我們的第一個斷點位于決議 delimiter 的地方:
進到方法內部看看:
可以看到邏輯非常簡單:
檢查是否傳入了自定義 delimiters:
-
如果沒有,setDelimiters 執行將沒有任何效果;也就是說,一定還有默認的值,稍后我們去驗證,
-
如果有,那么進行決議(如果為 null,默認使用 ${*} ),同時會判斷 useDefaultDelimiters 是否為 true,若為 true,就把默認 delimiters 加到結果集中,
那么我們順著找一下默認 delimiters:
發現是在初始化時設定的,
繼續追蹤,可以看到 delimiters 被決議為占位符:
PS:maven-resources-plugin 插件注釋中有相關說明:
然后開始逐字符讀取檔案 application.properties,只有發現字符匹配占位符時才處理:
由于我們自定義了 delimiter 為 #,并且把 useDefaultDelimiters 置為 false,因此 delimiters 中只有 #*# 這一種占位符,因此只有 # 這個字符才會被決議,而 ${ 、} 和 @ 都會被無視,
接下來進入 org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator#interpolate 中,將占位符替換為配置資料:
首先獲取即將被決議的占位符運算式:
接著獲取可用的占位符:
進入方法內部:
最后決議出配置資料:
然后回到上層,將占位符替換為配置資料:
到這里,占位符的決議程序就結束了,
至此,我們知道:maven-resources-plugin 插件根據我們傳入的配置資料,首先決議出可用的 delimiters,并將其轉換為占位符,最終用真實的配置資料進行替換,
總結
本文討論了 SpringBoot 專案中的占位符機制,結合實驗和原始碼進行了驗證,可以得出結論,對于 SpringBoot 使用的 maven-resources-plugin 3.2.0 (更低的版本可自行探索)來說:
-
默認占位符有兩種,分別是 ${*} 和 @*@
-
配置項 useDefaultDelimiters,可以控制是否使用默認占位符,如果為 true,則 ${*} 和 @*@ 這兩種占位符始終有效,可以同時使用
-
配置項 delimiter,既可以寫默認占位符,也可以自定義占位符,比如上文中的 #
注意事項:
-
占位符必須成對使用,如果忘記寫右邊的,則不會被決議,
-
本文搭建的 Demo 專案,使用了 spring-boot-starter-parent 作為 parent,但有時我們可能不會使用它,此時,maven-resources-plugin 插件需要我們手動引入,道理是一樣的,
常見情況:
-
如果專案直接或間接引入 spring-boot-starter-parent 作為 parent,且沒有手動配置 maven-resources-plugin 插件,則只能使用 @*@ 這一種占位符,這是在 spring-boot-starter-parent 指定的,
-
如果專案沒有引入 spring-boot-starter-parent 作為 parent,手動引入 maven-resources-plugin 插件,但沒有指定任何 delimiter,也沒有顯式配置 useDefaultDelimiters 為 false,那么可以使用默認占位符 @*@ 或 ${*},因為不配置 useDefaultDelimiters 的話,默認為 true,
『注:本文來自博客園“小溪的博客”,若非宣告均為原創內容,請勿用于商業用途,轉載請注明出處http://www.cnblogs.com/xiaoxi666/』
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/379379.html
標籤:其他
