1. 程式人生 > >【Spring Boot課程】3.Spring Boot的配置

【Spring Boot課程】3.Spring Boot的配置

1 yaml簡介

1.1 基本語法

k:(空格)v: 表示一對鍵值對(空格必須有)
以空格的縮排來控制層級關係;只要是做肚臍的一列資料,都是同一個層級的。

server:
  port: 8081
  path: /hello

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

1.2 值的寫法

1.2.1 字面量

1.2.1.1 普通的值(數字、字串、布林)

k:v;字面量直接來寫,字串不需要加上丹壹號或者雙引號;
雙引號:不會轉移字串李的特殊字元,特殊字元會作為本身想表示的意思。

name:"zhangsan\nlisi"

其輸出就是有換行。
單引號:會轉義特殊字元,特殊字元最終只是一個普通的字串資料。

name:"zhangsan\nlisi"

輸出為原樣。

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

物件還是k:v的方式,在下一行直接寫物件的屬性和值的關係,不過要注意縮排(兩個空格)。

friends:
  lastName: zhangsan
  age: 20

行內寫法:

friends: {lastName: zhangsan,age: 18}

1.2.1.3 陣列(List、Set)

短橫線(空格)值 表示陣列中的一個元素。

pets:
  - cat
  - dog
  - pig

行內寫法:

pets: [cat,dog,
pig]

1.3 進階語法

1.3.1 複合結構

person:
  lastName: zhangsan
  age: 18
  boss: false
  birth: 2017/12/12
  maps: {k1: v1,k2: 12}
  lists:
    - lisi
    - zhangsan
  dog:
    name: xiaogou
    age: 2

2 配置檔案

SB使用一個全域性配置檔案,配置檔名是固定的:

  • application.properties
  • application.yml

配置檔案的作用:修改SB自動配置的預設值。SB在底層都給我們自動配置好了;

配置檔案位於src/resources下面。

2.1 YAML

yaml a markup language 是一個標記語言,以資料為中心,比其他型別的標記語言更適合作為配置檔案。
標記語言:以前的配置檔案大多都使用XXX.xml檔案,

2.2 不同檔案配置方式

YAML:

server:
  port: 8081

XML:

<server>
  <port>8081</port>
</server>

properties:

server.port = 8081

3 例子:配置檔案實現值的注入

3.1 編寫配置檔案

person:
  lastName: zhaoyi
  list:
    - list1
    - list2
  maps:
    k1: v1
    k2: v2
  age: 25
  boss: false
  dog:
    name: xiaohuang
    age: 30 

配置元件

// person類
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private List<Object> list;
    private Map<String, Object> maps;
    private Integer age;
    private Boolean boss;
    private Dog dog;

    @Override
    public String toString() {
        return "Person{" +
                "lastName='" + lastName + '\'' +
                ", list=" + list +
                ", maps=" + maps +
                ", age=" + age +
                ", boss=" + boss +
                ", dog=" + dog +
                '}';
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public List<Object> getList() {
        return list;
    }

    public void setList(List<Object> list) {
        this.list = list;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getBoss() {
        return boss;
    }

    public void setBoss(Boolean boss) {
        this.boss = boss;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }
}

// dog類
package com.zhaoyi.hello1.com.zhaoyi.hello1.bean;

public class Dog {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.2 使配置擁有提示

匯入配置檔案處理器,這樣就會有提示編寫資源的資訊;

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

只有元件是容器中的元件,才能使用ConfigurationProperties提供的功能;

3.3 編寫測試類

SB單元測試:可以在測試期間很方便的類似編碼一樣進行自動注入等容器功能;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Hello1ApplicationTests {

    @Autowired
    Person person;

    @Test
    public void contextLoads() {
        System.out.println(person);
    }
}

4 properties配置檔案的編碼問題

4.1 使用properties配置檔案

註釋掉application.yml的配置檔案資訊,新建一個application.properties檔案,寫入和applicatio.yml相同功能的配置

person.age=14
person.last-name=張三
person.boss=false
person.maps.k1=v1
person.maps.k2=v2
person.list=a,b,c
person.dog.name=小狗
person.dog.age=5

執行測試後發現有關中文部分的輸出是亂碼。

4.2 解決方法

idea使用的是utf-8編碼,在setting處查詢file encoding,設定為utf-8,並選擇在執行時轉化為ascll(勾選)

5 @configurationProperties和@Value

Spring中配置一個Bean:

<bean class="person">
  <property name="lastName" value="zhangsan"></property>
</bean>

其中,value可以:

  • 字面量
  • ${key} 從環境變數或者配置檔案中提取變數值
  • #{SPEL}

而@Value其實效果和其一樣,比如,在類屬性上寫

@Value("person.lastName")
private String lastName
@Value("#{11*2}")
private Integer age

5.1 區別

@ConfigurationProperties @Value
功能 批量注入配置檔案中的屬性 一個個指定
支援鬆散繫結 不支援鬆散繫結
SpEL 不支援 支援
JSR303資料校驗 支援 不支援
複雜型別封裝 支援 不支援

注:

  • 鬆散繫結例如:
    配置檔案填寫的是last-name,而@Value必須寫一模一樣的,否則會報錯。而使用@ConfigurationProperties則可以使用駝峰式(其實就是類的屬性名)
  • @Validation 在類上加入此配置之後,開啟jsr303校驗,例如在某欄位上加上@Email,則配置檔案對應欄位必須符合郵箱格式。相反,如果此處我們使用@Value注入值,可以看到,可以正常的注入,即便提供的不符合郵箱格式,也不會報錯。

5.2 選擇

從上面的說明我們可以知道,兩者在配置上基本可以相互替換,彼此功能也大體一致,那麼,我們在業務場景中,該選用哪一種進行編碼呢?

  1. 如果說,我們只是在某個業務邏輯中需要獲取一下配置檔案的某項值,考慮使用@Value.
@RestController
public class HelloController {
    @Value("${person.last-name}")
    private String name;

    @RequestMapping("/")
    public String hello(){
        return "hello world, " + name;
    }
}
  1. 但是,如果我們專門去寫一個java bean來和配置檔案進行對映,那麼毫無疑問,我們應該使用@ConfigurationProperties

6 @PopertySource註解

@PropertySource 載入指定的配置檔案。

我們知道,@ConfigurationProperties預設從全域性配置檔案(application.properties)中獲取值,但通常我們會將相關的配置檔案放到某個配置檔案中,例如,我們將有關person的配置資訊放到person.properties中去。為了使元件類Person能夠找到配置檔案的配置資訊,需要使用增加新的註解@PropertySource指定從哪裡載入配置等相關資訊。

@PropertySource("classpath:person.properties")
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
}

7 @ImportResource註解

@ImportResource 匯入Spring的配置檔案,讓配置檔案裡面的內容生效。

  1. 新增新的服務類HelloService
package com.zhaoyi.hello1.service;
public class HelloService {
}
  1. 在類目錄下建立一個bean.xml,即spring的配置檔案,其中配置了一個service的bean.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloService" class="com.zhaoyi.hello1.service.HelloService"></bean>
</beans>

那麼問題來了,在spring-boot中顯然是不會載入此bean的,我們測試一下。

  1. 編寫測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class Hello1ApplicationTests {
    @Autowired
    ApplicationContext ioc;

    @Test
    public void testBean(){
        System.out.println("Is have helloService bean? " + ioc.containsBean("helloService"));
    }
}

測試結果:

Is have helloService bean? false

Spring boot裡面是沒有Spring的配置檔案,我們自己編寫的配置檔案,也不能自動識別,想讓Spring的配置檔案生效,則需要手動指示將其載入進行,使用@ImportResource標註在一個配置類(例如應用程式啟動類,他也是一個配置類)上:

@ImportResource(locations = {"classpath:bean.xml"})
@SpringBootApplication
public class Hello1Application {
    public static void main(String[] args) {
        SpringApplication.run(Hello1Application.class, args);
    }
}

這時候執行測試用例就會發現,bean已經出現在容器中了。

7 @Bean註解

@ImportResource(locations = {“classpath:bean.xml”})

一般我們不會使用6中所提到的這種方式,因為xml配置方式實在是寫了太多的無用程式碼,如果xml的標籤宣告,以及頭部的域名空間導致。因此,SpringBoot推薦給容器中新增元件的方式:全註解方式。也就是用配置類來充當bean配置檔案。如下,即為一個配置類:

/**
 * @Configuration 指明當前類是一個配置類
 */
@Configuration
public class MyConfig {
    // 將方法的返回值新增到容器中:容器中這個元件的id就是方法名
    @Bean
    public HelloService helloService(){
        return new HelloService();
    }
}

通過該配置類,可以為容器中添加了一個名為helloService的bean。

8 配置檔案佔位符

無論是使用yaml還是properties都自持檔案佔位符配置方式

8.1 隨機數

${random.value}

${random.int}

${random.long}

${random.int(10)}

${random.int[1024,65536]}

8.2 屬性佔位符

  • 可以在配置檔案中引用前面配置過的屬性;
  • ${app.name:預設值} 若找不到屬性時,則取預設值處填寫的值;

8.3 例子

  1. 在person.properties中寫入如下配置
person.age=14
person.last-name=張三${random.uuid}
person.boss=false
person.maps.k1={person.xxx:novalue}
person.maps.k2=v2
person.list=a,b,c
person.dog.name=${person.last-name}_小狗狗
person.dog.age=${random.int}
  1. 測試輸出
Person{lastName='張三1b9fbbb1-6c58-4a35-8165-ad23800d7456', list=[a, b, c], maps={k2=v2, k1=novalue}, age=14, boss=false, dog=Dog{name='張三b3a355d7-54ce-4afd-9ae6-d3be4aeb4165_小狗狗', age=-122850975}}

注意:留意預設值那一項設定,我們如願的成功設定了預設值novalue。在實際專案中,這種情況比較常用,稍微留意一下。如果沒有預設值,則會將${xxx}這一段作為值,這顯然是錯誤的。

9 Profile

profile一般是spring用來做多環境支援的,可以通過啟用指定引數等方式快速的切換當前環境。

9.1 多Profile檔案

我們在主配置檔案編寫的時候,檔名可以是 application-{profile}.properties(yml)
例如:
application-dev.properties、application-prod.properties等。

9.2 啟用指定profile

  1. 在application.properties中指定:
spring.profiles.active=dev
  1. 命令列方式啟用,此配置的優先順序高於配置檔案處的配置。
--spring.profiles.active=dev
  • 方式一 點選編譯環境右上角的下拉框,選擇第一項edit configuration,在environment配置節中的Program arguments寫入spring.profiles.active=dev即可。
  • 方式二 將當前的專案打包生成jar包,然後執行java -jar your-jar-name.jar --spring.profiles.active=dev即可。
  1. 虛擬機器引數方式啟用 在步驟2的老地方,Program arguments上一項即為虛擬機器引數啟用,不過填寫的內容為-Dspring.profiles.active=dev,即多了一個-D而已。

9.3 yml多文件配置檔案

若我們使用properties,則需要編寫多個不同的配置檔案,但如果我們使用yml的話,則可以通過多文件配置節實現單檔案管理。注:有關yml相關的知識參考此文件的前半部分

可以看到,我們通過---三個橫線實現文件分割,同時在每一個文件塊處指定了各個profile的不同配置資訊,即dev環境下啟動服務使用8082埠,prod環境下使用8083埠。而指定哪一個profile則是通過預設的文件塊(即第一塊)中的spring.profiles.active進行配置。完成如上配置之後我們啟動服務,顯然此時是以啟用的prod環境所配置的埠8083執行的,如下啟動日誌所示:

com.zhaoyi.hello1.Hello1Application      : The following profiles are active: prod
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8083 (http)

也就說,當我們選定一個profile後,對應的文件塊的配置就會全部生效。

9 配置檔案的載入位置

SpringBoot啟動會掃描以下位置的application.properties或者application.yml檔案作為SpringBoot的預設配置檔案:

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

以上是按照優先順序順序從高到低,所有位置的檔案都會被載入,但對於相同配置:高優先順序配置內容會覆蓋低優先順序配置的內容,其他的則互補。

我們也可以通過spring.config.location引數來設定預設的配置檔案位置。專案打包好以後,使用命令列引數形式來指定配置檔案的新位置,指定的配置檔案和預設載入的配置互補起作用。並且我們指定的該配置檔案優先順序是最高的。

10 外部配置的載入順序

SpringBoot支援多種外部配置方式,他可以從以下位置載入配置,按優先順序從高到低排列如下(高優先順序配置覆蓋低優先順序配置,所有配置會形成互補配置):

  1. 命令列引數
java -jar package_name_version.jar --server-port=8080 --server.context-path=/hello 

多個引數之間用空格分開,用--parameter_name=value進行配置。

  1. 來自java:comp/env的JNDI屬性
  2. java系統屬性(System.getProperties())
  3. 作業系統環境變數
  4. RandomValuePropertySource配置的random.*屬性值

都是由jar包外向jar包內進行尋找,高優先順序的配置覆蓋低優先順序的配置。然後
優先載入帶profile的:

  1. jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案

在jar檔案的同級目錄放一個application.properties檔案,其配置內容會被載入;

  1. jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案

再來載入不帶profile的:

  1. jar包外部的application.properties或application.yml(不帶spring.profile)配置檔案
  2. jar包內部的application.properties或application.yml(不帶spring.profile)配置檔案
  3. @Configuration註解類上的@PropertySource
  4. 通過SpringApplication.setDefaultProperties指定的預設屬性。

官方文件列出了比這裡更多的配置文件,請參考,版本更迭地址會經常變動,可自行前往官方網站進行檢視。

11 自動配置原理

配置檔案的配置屬性可以參照官方文件:前往

11.1 自動配置原理

  1. SB啟動的時候載入主配置類,開起了自動配置功能@EnableAutoConfiguration
  2. @EnableAutoConfiguration作用:利用EnableAutoConfigurationImportSelector給容器中匯入一些元件,可以檢視selectImport()方法的內容:
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

掃描所有jar包類路徑下META-INF/spring.factories檔案,吧掃描到的這些檔案的內容包裝成properties物件;從properties中獲取到EnableAutoConfiguration.class類對應的值,然後把他們新增在容器中;

說到底,就是將類路徑下META-INF/spring.factories裡面配置的所有EnableAutoConfiguration的值加入到了容器中。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
...(more)

每一個這樣的xxxAutoConfiguration類都會是容器中的一個元件,都加入到了容器中,用他們來做自動配置。

  1. 每一個自動配置類進行自動配置功能。
    org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration為例:

@Configuration
@EnableConfigurationProperties({HttpProperties.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
   ...
}

我們可以看到該類的註解如下:

  • @Configuration 毫無疑問,表明這是一個配置類,和我們要自定義一個配置檔案一樣,實現給容器中新增元件;
  • @EnableConfigurationProperties 啟動指定類的ConfigurationPropertiesg功能,這樣就可以將配置檔案中對應的值和HttpEncodingProperties繫結起來;並把HttpEncodingProperties加入到ioc容器中;
  • @ConditionalOnWebApplication Spring底層@Conditional註解,根據不同的條件,如果滿足指定的條件,整個配置類裡面的配置就會生效;此處判斷的就是當前應用是不是Web應用;否則配置不生效;
  • @ConditionalOnClass 判斷當前專案有沒有類(CharacterEncodingFilter),這是SpringMVC中進行亂碼解決的過濾器;
  • @ConditionalOnProperty 判斷配置檔案中是否存在某個配置 spring.http.encoding.enabled;如果不存在,則判斷成立,如果沒有配置,則此處將其設定為true
  • @Bean 給容器中新增Bean元件,該元件的某些值需要從properties中獲取,此處即為HttpEncodingProperies,顯然此刻他的取值已經和SB的properties檔案進行注入了;

根據不同的條件進行判斷當前這個配置類是否生效。一單這個配置類生效,這個配置類就會給容器中新增各種元件;這些元件的屬性均來自於其對應的Properties類的,這些Properties類裡面的每一個屬性,又是和配置檔案繫結的。

  1. 所有在配置檔案中能配置的屬性都是在xxxProperties類中的封裝者;配置檔案能配置什麼就可以檢視對應的屬性類。如上面的這個配置類我們就可以參考他的註解@EnableConfigurationProperties指定的properties類HttpProperties
@ConfigurationProperties(
    prefix = "spring.http"
)
public class HttpProperties {

11.2 配置精髓

  1. SB啟動會載入大量的自動配置類;
  2. 我們看我們需要的功能有沒有SB預設寫好的自動配置類;
  3. 我們再來看這個自動配置類中到底配了那些元件,倘若已經有了,我們就不需要再編寫配置檔案進行配置了。
  4. 給容器中自動配置類新增元件的時候,會從對應的Properties類中獲取某些屬性。我們就可以在配置檔案中指定這些屬性的值(其已經通過註解進行了繫結);

11.3 總結

xxxAutoConfiguration 這種類就是用來做自動配置的,他會給容器中新增相關的元件,其對應的Properties則對應了配置的各種屬性;也就是說,通過這些配置類,我們以前需要在SpringMVC中寫配置類、檔案實現的東西,現在,只需要在Properites配置檔案中加入相關的配置即可,不再那麼麻煩了。

當然,一些特殊的配置還是得自己寫元件的哦。

12 @Conditional*相關注解

12.1 @Conditinal派生註解

只有其指定的條件成立,配置類的所有型別才會生效,更小範圍的,例如註釋在某個Bean元件上面的相關條件註解成立,才會生成該Bean。

12.2 常用派生註解一覽

@Conditional擴充套件註解 作用(判斷是否滿足當前指定條件)
@ConditionalOnJava 系統的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 滿足SpEL表示式指定
@ConditionalOnClass 系統中有指定的類
@ConditionalOnMissingClass 系統中沒有指定的類
@ConditionalOnSingleCandidate 容器中只有一個指定的Bean,或者這個Bean是首選Bean
@ConditionalOnProperty 系統中指定的屬性是否有指定的值
@ConditionalOnResource 類路徑下是否存在指定資原始