quarkus實戰之七:使用設定

2023-07-27 09:00:29

歡迎存取我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本文是《quarkus實戰》系列的第七篇,前文講述瞭如何在將設定資訊傳入quarkus應用,今天要練習的是如何使用這些設定資訊
  • 整篇文章由以下內容構成:
  1. 建立工程,作為演示使用設定項操作的程式碼
  2. 演示最基本的使用設定項操作
  3. 展示設定項不存時會導致什麼問題
  4. 演示如何設定預設值,這樣設定項不存在也不會出錯
  5. 預設值是字串,而實際的變數可以是多種型別,它們之間的關係
  6. Optional型別的設定注入
  7. 不用註解注入,也可以寫程式碼獲取設定
  8. 針對相同字首的設定項,使用設定介面簡化程式碼
  9. 使用設定介面巢狀,簡化多級的相同字首設定項
  10. 用map接受設定資訊(減少設定項相關程式碼量)
  11. quarkus及其擴充套件元件的內建設定項
  • 接下來從建立demo工程開始吧

演示程式碼

  • 建立一個demo工程,參考下面的命令,這樣的工程會自帶一個web服務類HobbyResource.java
mvn "io.quarkus:quarkus-maven-plugin:create" \
  -DprojectGroupId="com.bolingcavalry" \
  -DprojectArtifactId="hello-quarkus" \
  -DprojectVersion="1.0-SNAPSHOT" \
  -DclassName="HobbyResource" \
  -Dpath="actions"

最基本的設定

  • 先來看看最常用最基本的組態檔使用方式
  • 開啟檔案src/main/resources/application.properties,增加下面這行設定
greeting.message = hello from application.properties
  • 開啟HobbyResource.java,增加如下成員變數,使用了註解ConfigProperty
  @ConfigProperty(name = "greeting.message") 
  String message;
  • 以上就是最簡單的使用設定項的方式,程式執行後,application.properties中greeting.message的值就會被quarkus框架注入到message成員變數中

設定項不存在導致的異常

  • 如果ConfigProperty註解的設定項在組態檔中不存在,應用啟動會報錯,來看看是什麼錯誤
  • 將HobbyResource.java的程式碼改成下面這樣,成員變數notExistsConfig的設定項是not.exists.config,這個設定項在組態檔中並不存在
@Path("/actions")
public class HobbyResource {

    // 組態檔中不存在名為not.exists.config的設定項
    @ConfigProperty(name = "not.exists.config")
    String notExistsConfig;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy, " + LocalDateTime.now() + ", [" + notExistsConfig + "]";
    }
}
  • 啟動應用時報錯如下圖所示,紅框中提示載入設定項失敗

帶預設值的設定

  • 對於上面演示的設定項不存在導致啟動失敗問題,可以給ConfigProperty註解設定預設值,這樣一旦找不到設定項,就使用預設值注入,可以避免啟動失敗了
  • HobbyResource.java的原始碼如下,成員變數notExistsConfig的註解了增加屬性defaultValue
@Path("/actions")
public class HobbyResource {

    // 組態檔中不存在名為not.exists.config的設定項
    @ConfigProperty(name = "not.exists.config", defaultValue = "112233")
    String notExistsConfig;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy, " + LocalDateTime.now() + ", [" + notExistsConfig + "]";
    }
}
  • 再次啟動應用,這次不報錯了,瀏覽器存取結果如下圖,defaultValue已經生效

defaultValue屬性的自動轉換

  • 對於ConfigProperty註解的defaultValue屬性還有一點要注意,來看ConfigProperty的原始碼,如下圖,紅框顯示defaultValue的型別是String
  • 上圖中,defaultValue的註釋有說明:如果ConfigProperty註解修飾的變數並非String型,那麼defaultValue的字串就會被自動quarkus字元轉換
  • 例如修飾的變數是int型,那麼defaultValue的String型別的值會被轉為int型再賦給變數,如下所示,notExistsConfig是int型,defaultValue的字串可以被轉為int:
// 組態檔中不存在名為not.exists.config的設定項
@ConfigProperty(name = "not.exists.config", defaultValue = "123")
int notExistsConfig;
  • 如果把上面程式碼中的defaultValue的值從123改為xxx,此時應用啟動就會失敗,因為「xxx」轉為int的過程中丟擲了異常,如下圖:
  • 除了上面試過的int,還有很多種型別都支援從defaultValue的字串值被自動轉換,它們是:
  1. 基礎型別:如boolean, byte, short
  2. 裝箱型別:如java.lang.Boolean, java.lang.Byte, java.lang.Short
  3. Optional型別:java.util.Optional, java.util.OptionalInt, java.util.OptionalLong, and java.util.OptionalDouble
  4. java列舉
  5. java.time.Duration
  6. JDK網路物件:如java.net.SocketAddress, java.net.InetAddress
  • 例如,下面是字串自動轉InetAddress的例子,可以正常執行:
@ConfigProperty(name = "server.address", defaultValue = "192.168.1.1")
InetAddress serverAddress;
  • 如果ConfigProperty修飾的變數是boolean型,或者Boolean型,則defaultValue值的自動轉換邏輯有些特別: "true", "1", "YES", "Y" "ON"這些都會被轉為true(而且不區分大小寫,"on"也被轉為true),其他值會被轉為false
  • 還有一處要注意的:defaultValue的值如果是空字串,就相當於沒有設定defaultValue,此時如果在組態檔中沒有該設定項,啟動應用會報錯

支援Optional

  • 支援Optional這個特性很贊,首先Optional型別的成員變數可直接用於函數語言程式設計,其次設定項不存在時又能避免啟動失敗
  • 接下來試試用ConfigProperty註解修飾Optional型別的成員變數
  • HobbyResource.java的原始碼如下,optionalMessage是Optional型別的成員變數,設定項optional.message就算不存在,應用也能正常啟動,並且optionalMessage直接用於函數語言程式設計中(optionalMessage.ifPresent)
@Path("/actions")
public class HobbyResource {

    // 組態檔中存在名為greeting.message的設定項
    @ConfigProperty(name = "greeting.message")
    String message;

    // 組態檔中,不論是否存在名為optional.message的設定項,應用都不會丟擲異常
    @ConfigProperty(name = "optional.message")
    Optional<String> optionalMessage;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        List<String> list = new ArrayList<>();
        list.add(message);

        // 只有設定項optional.message存在的時候,才會執行list.add方法
        optionalMessage.ifPresent(list::add);

        return "Hello RESTEasy, " + LocalDateTime.now() + ", " + list;
    }
}
  • 先看設定項optional.message存在的情況,如下圖紅框所示,optional.message在組態檔中是個正常的設定項
  • 啟動應用,瀏覽器存取web介面,如下圖,optional info是設定項optional.message的值
  • 現在將optional info從檔案application.properties中刪除,重啟應用,再次存取瀏覽器,如下圖,應用依然正常響應,list中只有成員變數message的內容:

編碼獲取設定項

  • 除了用ConfigProperty註解來獲取設定項的值,還可以用寫程式碼的方式獲取
  • 下面的程式碼展示了通過API獲取設定項的操作,請注意程式碼中的註釋
@Path("/actions")
public class HobbyResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        List<String> list = new ArrayList<>();

        // 可以用靜態方法取得Config範例
        Config config = ConfigProvider.getConfig();

        // getValue可取得指定設定項的指定型別值
        String greet = config.getValue("greeting.message", String.class);

        list.add(greet);

        // getOptionalValue可以將設定項的值包狀為Optional物件,如果設定項不存在,也不會報錯
        Optional<String> optional = config.getOptionalValue("not.exists.config", String.class);

        // 函數語言程式設計:只用optional中有物件時,才會執行list.add方法
        optional.ifPresent(list::add);

        return "Hello RESTEasy, " + LocalDateTime.now() + ", " + list;
    }
}
  • 當設定項not.exists.config不存在時,頁面響應如下,只有greeting.message設定項的值:
  • 設定項not.exists.config=123456時,頁面響應如下,兩個設定項的值都能成功獲取:
  • 另外,官方建議不要使用System.getProperty(String) System.getEnv(String)去獲取設定項了,它們並非quarkus的API,因此quarkus設定相關的功能與它們並無關係(例如感知設定變化、自動轉換型別等)

設定介面

  • 假設設定項如下,都是相同的字首student
student.name=Tom
student.age=11
student.description=He is a good boy
  • 針對上述設定項,可以用註解ConfigMapping將這些它們集中在一個介面類中獲取,介面類StudentConfiguration.java如下
package com.bolingcavalry;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;

@ConfigMapping(prefix = "student")
public interface StudentConfiguration {
    /**
     * 名字與設定項一致
     * @return
     */
    String name();

    /**
     * 名字與設定項一致,自動轉為int型
     * @return
     */
    int age();

    /**
     * 名字與設定項不一致時,用WithName註解指定設定項
     * @return
     */
    @WithName("description")
    String desc();

    /**
     * 用WithDefault註解設定預設值,如果設定項"student.favorite"不存在,則預設值生效
     * @return
     */
    @WithDefault("default from code")
    String favorite();
}
  • 從上述程式碼可見,一個介面即可完成所有設定項的注入,在使用這些設定項的時候,只要注入StudentConfiguration範例即可
  • 首先要用ConfigMapping指明設定項的字首,該介面中的方法都對應具有此字首的設定項
  • 一般情況下,方法名就等於設定項的名稱,也可以用WithName指定設定項名稱
  • WithDefault指定預設值,如果找不到設定項就用此預設值
  • 來看看如何使用這個設定介面,web服務程式碼如下,只要依賴注入StudentConfiguration即可,不在需要為每個設定項都用成員變數和ConfigProperty註解了
package com.bolingcavalry;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/actions")
public class HobbyResource {

    @Inject
    StudentConfiguration student;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {

        return "Hello RESTEasy, "
                + LocalDateTime.now()
                + " [" + student.name() + "], "
                + " [" + student.age() + "], "
                + " [" + student.desc() + "], "
                + " [" + student.favorite() + "]";
    }
}
  • 執行程式碼,用瀏覽器存取web介面試試,如下圖,所有設定項都能正確獲取

設定項是多個單詞時,如何對應設定介面的方法?

  • 回顧剛才的設定,name、age、description這些都是單個單詞,現在如果有個設定項是多個單詞,例如學號的英文是student number,應該如何轉為StudentConfiguration介面的方法呢?
  • 首先要看您的匹配項的命名風格,對多個單詞是如何分隔的,一般有這三種:
  1. 減號分隔:student-number
  2. 下劃線分隔:student_number
  3. 駝峰命名:studentNumber
  • ConfigMapping註解提供了namingStrategy的屬性,其值有三種,分別對應上述三種命名風格,您根據自身情況選用即可
  1. KEBAB_CASE(預設值):減號分隔的設定項轉為駝峰命令的方法,設定項student-number對應的方法是studentNumber
  2. SNAKE_CASE:下劃線分隔的設定項轉為駝峰命令的方法,設定項student_number對應的方法是studentNumber
  3. VERBATIM:完全對應,不做任何轉換,設定項student_number對應的方法是student_number
  • 使用namingStrategy屬性的範例程式碼如下
@ConfigMapping(prefix = "student", namingStrategy = ConfigMapping.NamingStrategy.SNAKE_CASE)
public interface StudentConfiguration {
    /**
     * 名字與設定項一致
     * @return
     */
    String name();
    ...

設定介面巢狀

  • 再來看下面的設定,有兩個設定項的字首都是student.address,給人的感覺像是student物件裡面有個成員變數是address型別的,而address有兩個欄位:province和city
student.name=Tom
student.age=11
student.description=He is a good boy

student.address.province=guangdong
student.address.city=shenzhen
  • 針對上述設定,quarkus支援用介面巢狀來匯入,具體做法分為兩步,首先新增一個介面Address.java,原始碼如下
package com.bolingcavalry;

public interface Address {
    String province();
    String city();
}
  • 第二步,在設定介面StudentConfiguration.java中,增加下圖紅框中的一行程式碼(介面中返回介面,形成介面巢狀)

  • 最後,修改HobbyResource.java程式碼,增加下圖紅框中的兩行,驗證能否正常取得address字首的設定專案

  • 重啟應用,如下圖,設定項可以正常獲取

設定項轉為map

  • 前面的介面巢狀,雖然將多層級的設定以物件的形式清晰的表達出來,但也引出一個問題:設定越多,介面定義或者介面方法就越多,程式碼隨之增加
  • 如果設定項的層級簡單,還有種簡單的方式將其對映到設定介面中:轉為map
  • 依然以下面的設定項為例
student.address.province=guangdong
student.address.city=shenzhen
  • 對應的程式碼改動如下圖,只要把address方法的返回值從Address改為Map<String, String>即可,這樣修改後,address層級下面再增加設定項,也不用修改設定項有關的程式碼了:

  • 使用設定的業務程式碼也要改,如下圖,改為從map中獲取

  • 部署執行驗證,可以正常取值

內建設定項

  • quarkus有很多內建的設定項,例如web服務的埠quarkus.http.port就是其中一個,如果您熟悉SpringBoot的話,對這些內建設定項應該很好理解,資料庫、訊息、快取,都有對應設定項

  • 篇幅所限就不在此講解quarkus內建的設定項了,您可以參考這份官方提供的設定項列表,裡面有詳細說明:https://quarkus.io/guides/all-config

  • 上述檔案中,有很多設定項帶有加鎖的圖示,如下圖紅框所示,有這個圖示的設定項,其值在應用構建的時候已經固定了,在應用執行期間始終保持唯讀狀態

  • 這種帶有加鎖圖示的設定項的值,在應用執行期間真的不能改變了嗎?其實還是有辦法的,官方檔案指明,如果業務的情況特殊,一定要變,就走熱部署的途徑,您可以參考《quarkus實戰之四:遠端熱部署》

  • 官方對開發者的建議:在開發quarkus應用的時候,不要使用quarkus作為設定項的字首,因為目前quarkus框架及其外掛們的設定項的字首都是quarkus,應用開發應該避免和框架使用相同的設定項字首,以免衝突

  • 至此,咱們已經學習瞭如何在quarkus應用中使用設定項,接下來還會一起實踐更多的quarkus基礎知識,鎖定《quarkus實戰》專輯,欣宸不會辜負您的期待

歡迎關注部落格園:程式設計師欣宸

學習路上,你不孤單,欣宸原創一路相伴...