2025/03/03

SpringBoot3 Configuration

Spring Boot 3 的相關設定

設定類別

spring 3.0 以前,都是使用 XML 設定檔。3.0 以後,是透過 @Configuration 註解的類別做設定。

@SpringBootApplication 裡面包含了 @SpringBootConfiguration,該設定可代替 @Configuration

example: @Bean 就類似以前 xml 的 <bean>

@SpringBootConfiguration
public class MainConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

SprintBoot 可使用一個 @SpringBootConfiguration或是 @Configuration 類別做設定,但也可以區分不同功能的設定,然後再使用 @Import 彙整在一起。

@SpringBootConfiguration
@Import({Config1.class, Config2.class})
public class MainConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

但如果設定類別都在類別掃描路徑上,可以用 @ComponentScan 掃描所有 packages。因 @SpringBootConfiguration 包含了 @ComponentScan,Application 以下的所有 packages。


也可以用 @ImportResource 匯入 XML 設定檔

設定檔

Spring Boot 的主要設定檔在 application.properties

如果有使用 Spring Cloud,有另一個 bootstrap.properties,該設定檔的優先權比 application.properties 高。

  • bootstrap 由 ApplicationContext 載入,優先權高

  • bootstrap 的設定不能被覆寫

設定檔可以用 .properties 或是 .yml (yaml 格式)

server.port = 8090
server.servlet.contextPath = /test

可使用 @PropertySource 匯入

.yml

server:
  port: 8090
  servlet:
    contextPath: /test

可用 YamlPropertiesFactoryBean 轉換為 Properties,或是用 YamlMapFactoryBean 轉換為 Map。也可以用 YamlPropertySourceLoader 在入為 PropertySource


@ConfigurationProperties 支援兩種格式的設定檔


設定綁定

所有已載入 Spring 的設定,都可以用 Environment 取得

@Autowired
private Environment env;

//
String getProperties(String key);

使用 @Value

使用 @Value

  1. ${ property : default_value }

  2. #{ obj.property ?: default_value }

example:

application.properties

jdbc.driverClass=com.mysql

在類別中,可用以下方式取得

    @Value("${jdbc.driverClass}")
    private String driver;

也可以用 @PropertySource

@Data
@Component
@PropertySource(value={"/config/db.properties"})
public class DBProperties {
    @Value("${jdbc.driverClass}")
    private String driverClass;
}

映射到類別、Constructor

可直接以 @ConfigurationProperties 將設定映射到一個類別

ex:

application.yaml

demo1:
  name: test
  users:
    - user1
    - user2
  params:
    place : userplace

demo2:
  name: test2
  age: 11
  birthday: 2024/11/11 11:11:11

demo3:
  name: test3
  age: 22

Demo1.properties

package tw.com.test.demo1;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;
import java.util.Map;

@Data
@ConfigurationProperties(prefix="demo1")
public class Demo1Properties {
    private String name;
    private List<String> users;
    private Map<String, String> params;
}

Demo2Properties.java

package tw.com.test.demo1;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

@Data
@NoArgsConstructor
@ConfigurationProperties(prefix="demo2")
public class Demo2Properties {
    private String name;
    private int age;
    private Date birthday;

    @ConstructorBinding
    public Demo2Properties(String name,
                           @DefaultValue("1") int age,
//                           @DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date birthday,
                           @JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss", timezone="GMT+8") Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
}

Demo1Application.java

package tw.com.test.demo1;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@RequiredArgsConstructor
@EnableConfigurationProperties(value = {Demo1Properties.class, Demo2Properties.class})
@Slf4j
public class Demo1Application {

    private final Demo1Properties demo1Properties;
    private final Demo2Properties demo2Properties;

    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner() {
        return (args) -> {
            log.info("demo1 properties: {}", demo1Properties);
            log.info("demo2 properties: {}", demo2Properties);
        };
    }
}

CommandLineRunner 的用途是,在 CLI 啟動 application 後,會被呼叫的 method

執行結果

demo1 properties: Demo1Properties(name=test, users=[user1, user2], params={place=userplace})
demo2 properties: Demo2Properties(name=test2, age=11, birthday=Mon Nov 11 11:11:11 CST 2024)

Bean 綁定

@ConfigurationProperties 除了用在 class,也可以用在 @Bean

MainConfig.java

@SpringBootConfiguration
public class MainConfig {
    @Bean
    @ConfigurationProperties(prefix="demo3")
    public Demo3Properties demo3Properties() {
        return new Demo3Properties();
    }
}

Demo3Properties.java

package tw.com.test.demo1;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Demo3Properties {
    private String name;
    private int age;
}

Demo1Application 不需要寫在 @EnableConfigurationProperties

@SpringBootApplication
@RequiredArgsConstructor
@EnableConfigurationProperties(value = {Demo1Properties.class, Demo2Properties.class})
@Slf4j
public class Demo1Application {

    private final Demo1Properties demo1Properties;
    private final Demo2Properties demo2Properties;
    private final Demo3Properties demo3Properties;

    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner() {
        return (args) -> {
            log.info("demo1 properties: {}", demo1Properties);
            log.info("demo2 properties: {}", demo2Properties);
            log.info("demo3 properties: {}", demo3Properties);
        };
    }
}

執行結果

demo3 properties: Demo3Properties(name=test3, age=22)

設定類別掃描

在 Application 加上 @ConfigurationPropertiesScan 可掃描該 package 以下的所有設定類別,如果要掃描特定package,就加上 basePackages

@SpringBootApplicaiton
@RequiredArgsConstructor
@ConfigurationPropertiesScan
public class Application {

}

設定驗證

使用 @ConfigurationProperties 可利用 JSR-303 javax.validation 驗證設定值

pom.xml 要加上套件

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

在設定欄位上加上 @NotNull

@Data
@Validated
@NoArgsConstructor
@ConfigurationProperties(prefix="demo2")
public class Demo2Properties {
    @NotNull
    private String name;

    private int age;
    private Date birthday;

外部設定

設定來源

  • properties

  • YAML

  • 環境變數

  • CLI 參數

設定的優先等級

由低到高

  1. 預設參數 SpringApplication.setDefaultProperties

  2. @PropertySource 綁定的設定

  3. 套用 application 檔案的參數

  4. 設定了 random.* 的參數

  5. 系統環境變數

  6. Java System Properties

  7. java:comp/env 的 JNDI 參數

  8. ServletContext 初始化參數

  9. ServletConfig 初始化參數

  10. 來自 SPRING_APPLICATION_JSON 的參數

  11. CLI 參數

  12. 單元測試裡面的參數

  13. 使用 @TestPropertySource 綁定的設定

  14. Devtools 全域設定參數,來自 $HOME/.config/spring-boot

多個設定檔案的優先等級,低到高

  1. jar 裡面的設定檔

  2. 指定了 profile 的設定檔,ex: application-dev.properties (jar 裡面)

  3. application 設定檔 (jar 外面)

  4. 指定了 profile 的設定檔,ex: application-dev.properties (jar 外面)


CLI 參數,是啟動時,用 -- 開頭的參數

ex:

java -jar application.jar --server.port=8090

用 maven 啟動

mvn spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=8090'

如果不希望把CLI 參數增加到 spring 環境中,可以禁用

public static void main(String[] args) {
    SpringApplication.setAddCommandLineProperties(false);
    SpringApplication.run(Application.class, args);
}

匯入設定檔

可利用 spring.config.import 指定匯入設定檔的路徑

spring:
  config:
    import:
      - optional:classpath:/config/app.yml

random

RandomVlauePropertySource 可用來注入亂數值,可以產生 int, long, uuid, string

ex:

demo1:
  age: ${random.int[10,100]}
  security:
    security-key: ${random.value}
    security-code: ${random.uuid}

多個設定環境

有可能遇到 正式、測試、開發環境不同的設定

yaml 用 --- 區隔

ex:

spring:
  application:
    name: "Demo1"
---
spring:
  application:
    name: "Production1"
  config:
    activate:
      on-cloud-platform: "kubernates"

properties

spring.application.name=Demo1
#---
spring.application.name=Production1
config.activate.on-cloud-platform=kubernates

以下設定檔,可限制只在 dev 或是 test 環境使用

  • spring.config.activate.on-profile

  • spring.config.activate.on-cloud-platform

spring:
  profiles:
    active: dev
---
spring:
  config:
    activate:
      on-profile: "dev|test"

Profile

profile可以分離設定

如果不指定時,就會使用預設的 Profiledefault,可以修改預設 profile

spring:
  profiles:
    default: dev

啟用 profile

啟用 dev, test

spring:
  profiles:
    active: dev, test

也可以在程式裡面啟用

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Demo1Application.class);
        springApplication.setAdditionalProfiles("dev", "test");
        springApplication.run(args);
    }

profile 可啟用設定跟 bean,可用在 @Component, @Configuration, @ConfigurationProperties

ex: 以下設定只有在啟用 "main" 時,才會載入這個設定

@Profile("main")
@SpringBootConfiguration
public class MainConfig {
}

yaml

spring:
  profiles:
    default: dev
    active: dev, main

@Profile 用在 @ConfigurationProperties 時要注意

  • 如果是用掃描的註冊方式,@Profile 可直接用在 @ConfigurationProperties

  • 如果是用 @EditConfigurationProperties,則要把 @Profile 用在 @EditConfigurationProperties 設定類別上


切換 Profile

spring.profiles.active 設定啟用的 profile

但還是能在 CLI 參數調整,因為 CLI 參數優先權比設定檔高

java -jar application.jar --spring.profiles.active=test
mvn spring-boot:run -Dapp.profiles=test

如果不希望被替代,可改用 include

spring:
  profiles:
    default: dev
    active: dev, main
    # include 的 profile 不會被覆蓋
    include:
      - dev
      - main

profile 分組

在 yaml 將 profile 分組,當 main 啟用時, main1, main2 也會被啟用

spring:
  profiles:
    default: dev
    active: dev, main
    # include 的 profile 不會被覆蓋
    include:
      - dev
      - main
    group:
      main:
        - main1
        - main2
spring:
  config:
    active:
      on-profile: main1
---
spring:
  config:
    active:
      on-profile: main2

指定 profile 的設定檔

application-${profile}

ex:

application.yml

application-dev.yml

application-test.yml

application-main.yml

active: dev, main優先順序 default > dev > main


使用限制

  • application.profiles.default

    不指定 profile 的預設 profile

  • application.profiles.active

    啟用的 profiles

  • application.profiles.include

    要包含的 profiles,不會被 CLI 參數覆蓋

  • application.profiles.group

    分組

這些參數不能用在多個設定環境跟指定 profile 設定檔

ex: 以下是錯誤用法

在 application.yml

---
---
spring:
  config:
    activate:
      on-profile: main2
  profiles:
    active: main

在 application-dev.yml 使用 application.profiles.active

spring:
  profiles:
    active: main

設定加密

Spring Boot 沒有提供標準加解密的方法,但有提供 EnvironmentPostProcessor interface,可在 application 啟動前控制 spring 環境

spring 加解密的方法:

  • 使用第三方設定中心,支援自動解密~~~~

  • 使用自訂加解密

  • 使用 Jasypt Spring Boot

第三方設定

Spring Cloud Config

需要加解密的內容,前面會加上 {cipher}

自訂加解密

模仿 spring cloud 的方法

spring:
  datasource:
  username: '{cipher}XXXXXXXX'

用以下方式解密

@Bean
public DataSource dataSource() {
    DataSource dataSource = new DruidDataSource();
    String username = this.getUsername();
    if( username.startWith("{cipher}") ) {
        username = Encrypt.decrypt( username, this.getKey() );
    }
    dataSource.setUsername(username);
    //...
    return dataSource;
}

使用 Jasypt Spring Boot

ref: GitHub - ulisesbocchio/jasypt-spring-boot: Jasypt integration for Spring boot

ref: Spring boot 開發 - 使用 Jasypt 進行加密 | roi's blog

可針對 Spring Boot 專案中的屬性,提供加解密的方案

設定升級

當 Spring Boot 更新版本後,某些設定參數可能已經被修改或刪除,在 pom 加上這個 library 就能自動分析。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-properties-migrator</artifactId>
        </dependency>

在啟動後,就可在 console 看到需要調整的參數

  1. 在程式啟動後,再加入 Spring 的設定參數,這個自動分析不支援。ex: @PropertySource 載入的設定

  2. 設定修改完成後,要將此 library 移除

沒有留言:

張貼留言