1. 程式人生 > 其它 >springboot學習二 配置檔案與自動配置

springboot學習二 配置檔案與自動配置

2.1、idea建立的maven專案結構

".gitignore": 用git做版本控制時 用這個檔案控制那些檔案或資料夾 不被提交(不用git的話可刪除 沒影響)
​
"HELP.md": md是一種文件格式 這個就是你專案的幫助文件(可刪除 沒影響)
​
"mvnw": linux上處理mevan版本相容問題的指令碼(可刪除 沒影響)
​
"mvnw.cmd": windows 上處理mevan版本相容問題的指令碼(可刪除 沒影響)
​
"專案名稱.iml": 有的檔案每個匯入IDEA的專案都會生成一個專案同名的.iml檔案 用於儲存你對這個專案的配置 (刪了程式重新匯入後還會生成 但由於配置丟失可能會造成程式異常)
​
"idea資料夾":不能刪除,刪除後也會重新生成
​
"src資料夾":如果作為父專案,其實用不到,可以刪除
​

2.1、建立聚合專案

1、建立一個簡單的maven專案,刪除多餘的檔案,只留下"pom.xml",".idea資料夾","專案名稱.iml檔案"
2、修改pom.xml檔案的中的打包方式,打包方式修改成pom
3、選中父專案,建立modules(子專案)

2.3、SpringApplication自定義

我們一般啟動springboot專案都是通過SpringApplication的run方法:

/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings.
     * @param source the source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object source, String... args) {
        return run(new Object[] { source }, args);
    }

這個run方法是SpringApplication中的靜態方法,實際上SpringApplication中還有例項方法,一些特定設定可以通過例項方法來設定。

public static void main(String[] args) {
        SpringApplication springApplication=new SpringApplication(ParamInterceptor.class);
        springApplication.setBannerMode(Banner.Mode.OFF);
        springApplication.run(args);
    }

2.4、配置檔案的使用

SpringBoot可以外部化配置,以便在不同的環境中使用相同的應用程式程式碼。您可以使用各種外部配置源,包括Java屬性檔案、YAML檔案、環境變數和命令列引數。屬性值可以通過使用 @Value 註解直接注入到你的 bean 中,通過 Spring 的 Environment 抽象訪問,或者通過 @ConfigurationProperties 繫結到結構化物件。Spring Boot 使用了一個非常特殊的 PropertySource 順序,該順序旨在允許合理地覆蓋值。 屬性按以下順序考慮(較低專案的值覆蓋較早的專案):

1. Default properties (specified by setting SpringApplication.setDefaultProperties).
​
2. @PropertySource annotations on your @Configuration classes. Please note that such property
sources are not added to the Environment until the application context is being refreshed. This is
too late to configure certain properties such as logging.* and spring.main.* which are read
before refresh begins.
​
3. Config data (such as application.properties files)
​
4. A RandomValuePropertySource that has properties only in random.*.
​
5. OS environment variables.
​
6. Java System properties (System.getProperties()).
​
7. JNDI attributes from java:comp/env.
​
8. ServletContext init parameters.
​
9. ServletConfig init parameters.
​
10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or
system property).
​
11. Command line arguments.
​
12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for
testing a particular slice of your application.
​
13. @TestPropertySource annotations on your tests.
​
14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is
active.

配置資料檔案的考慮順序如下:

1. Application properties packaged inside your jar (application.properties and YAML variants).
​
2. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
​
3. Application properties outside of your packaged jar (application.properties and YAML variants).
​
4. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)

約定大於配置。springboot中的預設配置檔案有三種:

"application.properties"
"application.yml"
"application.yaml"

且預設位置在resources目錄下,且名稱必須是application,如果配置檔名稱不是application的話,需要採取讀外部配置檔案的方式(spring.config.name),且此方式指定的配置檔案不會和別的配置檔案互補。

檢視程式碼
$ java -jar myproject.jar --spring.config.name="配置檔名稱"

2.4.1、yml配置檔案

yml配置檔案採取的是樹形結構,properties檔案採取的是k/v格式。

  • yml 基本語法

    • k: v:表示一對鍵值對(冒號與V之間有空格)

    • 空格的縮排來控制層級關係,只要是左對齊的一列資料,都是同一個層級的

    • 屬性和值都是大小寫敏感的

  • 值的寫法

    • 字面量:普通的值(數字、字串,布林)

    • k: v:字面直接來寫

      字串預設不用加上引號或者雙引號;不會轉義字串裡面的特殊字元;特殊字元會作為本身想表示的意思

      name: "張三 \n lisi“ ;輸出:張三 換行 lisi

    • 物件、Map(屬性和值)(鍵值對):

      k: v:

      物件還是k: v的方式:

      friends:
        last_name: wang
        first_name: gang

      還有一種行內寫法:

      friends: {last_name: wang,first_name: gang}
    • 陣列(List、Set):

      用”-“表示陣列中的一個元素

      pets:
        - cat
        - dog
        - pig

      也有行內寫法:

      pets: [pig,cat,dog]

2.4.2、yml檔案的注入

yaml配置檔案:

person:
  lastName: 張三
  age: 19
  boss: false
  birth: 2010/05/02
  maps: {k1:va,k2:v2}
  lists:
    - lisi
    - wangwu
    - zhaoliu
  dog:
    name: 小愛
    age: 5

需要注入的JavaBean

/**
 * @Description 將配置檔案中的配置的每一個屬性對映到這個元件中
 * @ConfigurationProperties:告訴SpringBoot將本類中的所有屬性和配置檔案中相關的配置進行繫結
 *  prefix = "person":配置檔案中那個下面的所有屬性進行一一對映
 *  
 * 只有這個元件是容器中的元件,才能容器提供的@ConfigurationProperties功能
 *
 **/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;
​
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

匯入配置檔案處理器,以後編寫配置就有提示了

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

optional可以防止當前依賴傳遞到其他模組中 ,optional選項在統一控制版本的情況下會失效。

2.4.3、配置檔案的載入順序

2.4.3.1、相同位置的配置檔案

在springboot的spring-boot-starter-parent.pom中定義了統一目錄下不同的配置檔案的載入的優先順序

<resources>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <excludes>
          <exclude>**/application*.yml</exclude>
          <exclude>**/application*.yaml</exclude>
          <exclude>**/application*.properties</exclude>
        </excludes>
      </resource>
    </resources>

從上述的配置檔案中可以看到,在相同位置時:.yml的優先順序大於.yaml,.yaml的優先順序大於.propertiess

2.4.3.2、不同位置的配置檔案

在idea的專案中,springboot的配置檔案位置可以放在如下列表中:

  • 優先順序由低到高

  • 最高的配置檔案為主

  • 優先順序低的配置檔案為輔,補充主配置檔案(spring.config.location指定的除外)

SpringApplication loads properties from application.properties files in the following locations and adds them to the Spring Environment:
1、"當前專案目錄下的一個/config子目錄"
2、"當前專案目錄"
    注意:如果是聚合專案或子專案,此處的當前專案都是指父專案
3、"專案的resources即一個classpath下的/config包"
4、"專案的resources即classpath根路徑(root)"
5、"通過spring.config.location環境變數來指定配置檔案的位置"
​
​
  • 位置使用示例:

  • 採用spring.config.location屬性指定示例:

採用此方式指定配置檔案,就只有指定的配置檔案會生效,其他未指定的配置檔案不會互補。

$ java -jar myproject.jar --spring.config.location=classpath:/default.properties
  • 如果有多個配置檔案,用逗號隔開

$ java -jar myproject.jar 
--spring.config.location=classpath:/default.properties,classpath:/override.properties

如果spring.config.location只指定了檔案位置,並未指定檔名稱,那麼spring.config.location的值必須以“/”結尾

$ java -jar myproject.jar --spring.config.location=d:\config/

2.4.3.3、配置資訊指定順序

SpringBoot 允許將配置外部化,以便您可以在不同環境中使用相同的應用程式程式碼。
外部化方式有:
1、".propertis檔案"
2、".ymal檔案"
3、".yml檔案"
4、"環境變數"
5、"命令列引數"
6、"虛擬機器引數"
​
springboot種的配置的值可以通過一下三種方式訪問:
1、通過@Value註解
2、通過spring中的AbstractEnvironment物件
3、通過@ConfigurationProperties註解,把配置的屬性的值注入到bean中
​

配置資訊指定方式的優先順序,從高到低

1、$HOME/.config/spring-boot當 devtools 處於活動狀態時,資料夾中的Devtools 全域性設定屬性。
​
2、@TestPropertySource 測試中的註釋。 單元測試中使用
​
3、properties屬性在您的測試中。可用於測試應用程式的特定部分@SpringBootTest的測試註釋。 單元測試中使用
​
4、命令列引數。
​
5、來自SPRING_APPLICATION_JSON(嵌入在環境變數或系統屬性中的內聯 JSON)的屬性。
​
6、ServletConfig 初始化引數。
​
7、ServletContext 初始化引數。
​
8、JNDI 屬性來自java:comp/env.
​
9、Java 系統屬性 ( System.getProperties())。
​
10、作業系統環境變數。
​
11、一RandomValuePropertySource,只有在擁有效能random.*。
​
12、打包的 jar(application-{profile}.properties和 YAML 變體)之外的特定於配置檔案的應用程式屬性。
​
13、打包在 jar 中的特定於配置檔案的應用程式屬性(application-{profile}.properties和 YAML 變體)。
​
14、打包 jar 之外的應用程式屬性(application.properties和 YAML 變體)。
​
15、打包在 jar 中的應用程式屬性(application.properties和 YAML 變體)。
​
16、@PropertySource你的@Configuration類的註釋。請注意,Environment在重新整理應用程式上下文之前,不會將此類屬性源新增到。現在配置某些屬性(例如在重新整理開始之前讀取的logging.*和)為時已晚spring.main.*。
​
17、預設屬性(由 setting 指定SpringApplication.setDefaultProperties)。

2.4.4、啟用特定配置檔案

很多時候,我們專案在開發環境和生成環境的環境配置是不一樣的,例如,資料庫配置,在開發的時候,我們一般用測試資料庫,而在生產環境的時候,我們是用正式的資料,這時候,我們可以利用profile在不同的環境下配置用不同的配置檔案或者不同的配置。

spring boot允許你通過命名約定按照一定的格式"application-{profile}.properties"來定義多個配置檔案,然後通過在"application.properyies"配置檔案中通過配置"spring.profiles.active"來具體啟用一個或者多個配置檔案.
​
如果沒有沒有指定任何profile的配置檔案的話,spring boot預設會啟動application-default.properties。
​

注意:此種方式是對同一位置的配置檔案,如果別的配置檔案位置的優先順序高,則以優先順序高的配置檔案為主。

2.5、屬性值的注入

2.5.1、@Value

@Value註解通常被用來向bean的屬性注入值

示例如下:

1、注入值

@Component
public class MovieRecommender {
  private final String catalog;
  public MovieRecommender(@Value("${catalog.name}") String catalog) {
  this.catalog = catalog;
  }
}

2、指定配置檔案

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

3、在類路徑下定義一個配置檔案

catalog.name=MovieCatalog

Spring提供了一個預設的寬鬆的嵌入式值解析器。它將嘗試解析屬性值,如果無法解析,則屬性名稱(例如${catlom.name})將作為該值注入。預設的解析器,當@Value中要注入的屬性不存在時,是不會報錯,會把@Value中的值注入到Bean中。如果要對這種不存在的值進行嚴格的控制,可以宣告一個PropertySourcesPlaceholderConfigurerbean,如下示例所示:

@Configuration
public class AppConfig {
  @Bean
  public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
     return new PropertySourcesPlaceholderConfigurer();
  }
}

配置好PropertySourcesPlaceholderConfigurer之後,如果再存在無@Value指定的配置的情況,spring直接啟動失敗。

注意
1、如果採用javaConfig方式配置PropertySourcesPlaceholderConfigurer,那麼這個@Bean方法必須是靜態方法。
​
2、如果無法解析任何 ${} 佔位符,則使用上述配置可確保 Spring 初始化失敗。 也可以使用 setPlaceholderPrefix、setPlaceholderSuffix 或setValueSeparator 自定義佔位符。
​
3、Spring Boot 預設配置一個 PropertySourcesPlaceholderConfigurer bean,它將從 application.properties 和 application.yml 檔案中獲取屬性
​

Spring 提供的內建轉換器支援簡單型別的自動轉換(例如到 Integer 或 int)。 多個逗號分隔的值也可以自動轉換為 String 陣列。

配置預設值

@Value支援配置預設值

@Component
public class MovieRecommender {
    
  private final String catalog;
    
  public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
     this.catalog = catalog;
  }
}
自定義型別轉換

Spring BeanPostProcessor 在幕後使用 ConversionService 來處理將 @Value 中的 String 值轉換為目標型別的過程。 如果您想為您自己的自定義型別提供轉換支援,您可以提供您自己的 ConversionService bean 例項,如下例所示:

@Configuration
public class AppConfig {
  @Bean
  public ConversionService conversionService() {
      DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
      conversionService.addConverter(new MyCustomConverter());
      return conversionService;
  }
}
支援SpEL

當@Value 包含一個 SpEL 表示式時,該值將在執行時動態計算。

@Component
public class MovieRecommender {
  private final String catalog;
  public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
    this.catalog = catalog;
  }
}

SpEL 還支援使用更復雜的資料結構:

@Component
public class MovieRecommender {
  private final Map<String, Integer> countOfMoviesPerCatalog;
  public MovieRecommender(@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
  this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
  }
}

2.5.2、@PropertySource

@PropertySource 註解為將 PropertySource 新增到 Spring 的 Environment中 提供了一種方便的宣告機制。

給定包含鍵值對 testbean.name=myTestBean 的 app.properties 檔案,以下 @Configuration 類使用 @PropertySource 的方式是呼叫 testBean.getName() 返回 myTestBean:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
  @Autowired
  Environment env;
  @Bean
  public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
  }
}

@PropertySource 資源位置中存在的任何 ${...} 佔位符都根據已經註冊在Environment進行解析,如以下示例所示:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
  @Autowired
  Environment env;
  @Bean
  public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
  }
}
​

假設 my.placeholder 存在於已註冊的屬性源(例如,系統屬性或環境變數)之一中,佔位符將解析為相應的值。 如果不是,則default/path用作預設值。 如果未指定預設值且無法解析屬性,則會引發 IllegalArgumentException。

注意:

根據 Java 8 約定,@PropertySource註解是可重複的。 但是,所有此類 @PropertySource 註解都需要在同一級別宣告,或者直接在配置類上宣告,或者作為自定義註解中的元註解。 不推薦混合直接註解和元註解,因為直接註解有效地覆蓋了元註解。

從歷史上看,元素中佔位符的值只能根據 JVM 系統屬性或環境變數進行解析。 這已不再是這種情況。 因為 Environment 抽象整合在整個容器中,所以很容易通過它路由佔位符的解析。這意味著您可以按照自己喜歡的任何方式配置解析過程。 您可以更改搜尋系統屬性和環境變數的優先順序或完全刪除它們。 您還可以根據需要將自己的屬性源新增到組合中。

具體來說,無論客戶屬性在哪裡定義,只要它在環境中可用,以下語句都有效:

<beans>
  <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

2.5.3、@ConfigurationProperties

使用 @Value("${property}") 註釋注入配置屬性有時會很麻煩,尤其是當您使用多個屬性或您的資料本質上是分層的時。 Spring Boot 提供了一種處理屬性的替代方法,讓強型別 bean 管理和驗證應用程式的配置。

可以繫結一個宣告標準 JavaBean 屬性的 bean,如下所示例子:

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.service")
public class MyProperties {
  private boolean enabled;
  private InetAddress remoteAddress;
  private final Security security = new Security();
  public boolean isEnabled() {
    return this.enabled;
  }
  public void setEnabled(boolean enabled) {
     this.enabled = enabled;
  }
  public InetAddress getRemoteAddress() {
    return this.remoteAddress;
  }
  public void setRemoteAddress(InetAddress remoteAddress) {
    this.remoteAddress = remoteAddress;
  }
  public Security getSecurity() {
     return this.security;
  }
  public static class Security {
      private String username;
      private String password;
      private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
      public String getUsername() {
         return this.username;
      }
      public void setUsername(String username) {
         this.username = username;
      }
      public String getPassword() {
        return this.password;
      }
      public void setPassword(String password) {
        this.password = password;
      }
      public List<String> getRoles() {
         return this.roles;
      }
      public void setRoles(List<String> roles) {
         this.roles = roles;
      }
  }
}

前面的 POJO 定義了以下屬性:

my.service.enabled
my.service.remote-address
my.service.security.username
my.service.security.password. 
my.service.security.roles

2.5.4、中文亂碼的解決

採用IDEA進行springboot開發時,在application.properties配置的中文亂碼

配置檔案如下:

server.port=8080
server.servlet.context-path=/shiro
spring.application.name=shiro
​
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
​
user.user-name=張三

對應繫結的配置類:

@Component
@ConfigurationProperties(prefix = "user")
@Data
public class User {
    private String userName;
}
​

執行結果:

User(userName=å¼ ä¸•)
​

在全使用UTF-8的情況下還是解決不了亂碼問題。

c.t.s.controller.UserController: User(userName=å¼ ä¸•)
 c.t.s.controller.UserController : å¼ ä¸•
​

讀取配置檔案的程式碼:

		log.info("{}",user);
        InputStream inputStream = UserController.class.getClassLoader().getResourceAsStream("application.properties");
​
        Properties properties=new Properties();
        try {
            properties.load(inputStream);
            String property = properties.getProperty("user.user-name");
            log.info("{}",property);
        } catch (IOException e) {
            e.printStackTrace();
        }

從上面的截圖,我們也可以看到,配置檔案是採用UTF-8,IDEA也是採用的UTF-8。那麼現在可以猜測,springboot在讀取檔案時,預設不是採用的utf-8方式讀取的。

InputStream inputStream = UserController.class.getClassLoader().getResourceAsStream("application.properties");
​
​
​
        Properties utf8Properties=new Properties();
        try {
            utf8Properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String property = utf8Properties.getProperty("user.user-name");
            log.info("{}",property);
​
        } catch (IOException e) {
            e.printStackTrace();
        }
​

執行結果:

2021-09-12 16:34:14.776  INFO 12028 --- [nio-8080-exec-2] c.t.s.controller.UserController          : 張三

當讀入的輸入流採用UTF-8進行解釋時,發現中文不再亂碼。這就更證明了springboot預設情況下對輸入流的解釋,採取的不是utf8。

如何指定springboot對輸入流的解釋方式呢。

server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

此時執行的結果就正常了:

INFO 27248 --- [nio-8080-exec-1] c.t.s.controller.UserController: User(userName=張三)
INFO 27248 --- [nio-8080-exec-1] c.t.s.controller.UserController: 張三