SpringBoot系列之@PropertySource讀取yaml文件

SpringBoot系列之@PropertySource支持yaml文件讀取html

最近在作實驗,想經過@PropertySource註解讀取配置文件的屬性,進行映射,習慣上用properties都是測試沒問題的,偶然換成yaml文件,發現都讀取不到屬性值java

由於yaml語法很簡潔,比較喜歡寫yaml配置文件,很顯然,@PropertySource默認不支持yaml讀取,咱們改爲@Value註解也是能夠讀取的,不過屬性一堆的話,一個一個讀取也是很繁瑣的,經過網上找資料和本身實驗驗證,發現是能夠實現對yaml支持git

而後,爲何@PropertySource註解默認不支持?能夠簡單跟一下源碼github

@PropertySource源碼:
在這裏插入圖片描述
根據註釋,默認使用DefaultPropertySourceFactory類做爲資源文件加載類
在這裏插入圖片描述
裏面仍是調用Spring框架底層的PropertiesLoaderUtils工具類進行讀取的
在這裏插入圖片描述
PropertiesLoaderUtils.loadProperties
在這裏插入圖片描述
從源碼能夠看出也是支持xml文件讀取的,能支持reader就獲取reader對象,不然出件inputStream
在這裏插入圖片描述spring

在這裏插入圖片描述
load0方法是關鍵,這裏加了同步鎖
在這裏插入圖片描述
很重要的load0 方法抓取出來:json

private void load0 (LineReader lr) throws IOException {
        char[] convtBuf = new char[1024];
        int limit;
        // 當前key所在位置
        int keyLen;
        // 當前value所在位置
        int valueStart;
        char c;//讀取的字符
        boolean hasSep;
        boolean precedingBackslash;//是否轉義字符,eg:/n etc.
        // 一行一行地讀取
        while ((limit = lr.readLine()) >= 0) {
            c = 0;
            keyLen = 0;
            valueStart = limit;
            hasSep = false;

            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
            precedingBackslash = false;
            //key的長度小於總的字符長度,那麼就進入循環
            while (keyLen < limit) {
                c = lr.lineBuf[keyLen];
                //need check if escaped.
                if ((c == '=' ||  c == ':') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    hasSep = true;
                    break;
                } else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    break;
                }
                if (c == '\\') {
                    precedingBackslash = !precedingBackslash;
                } else {
                    precedingBackslash = false;
                }
                keyLen++;
            }
            //value的起始位置小於總的字符長度,那麼就進入該循環
            while (valueStart < limit) {
                c = lr.lineBuf[valueStart];
                //當前字符是否非空格類字符
                if (c != ' ' && c != '\t' &&  c != '\f') {
                    if (!hasSep && (c == '=' ||  c == ':')) {
                        hasSep = true;
                    } else {
                        break;
                    }
                }
                valueStart++;
            }
            //讀取key
            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
            //讀取value
            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
            put(key, value);
        }
    }

ok,從源碼能夠看出,這個方法是一行一行地讀取,而後根據冒號、等於號、空格等進行校驗,通過一系列遍歷以後獲取key和value,而yaml語法是以縮進來辨別的,通過本身調試,這個方法也是不支持yaml文件的讀取的,properties源碼是比較多的,具體的Properties源碼實現的能夠參考博客:https://www.cnblogs.com/liuming1992/p/4360310.html,這篇博客寫的比較詳細api

ok,而後給個例子來實現對yaml配置文件的讀取springboot

# 測試ConfigurationProperties
user:
  userName: root
  isAdmin: true
  regTime: 2019/11/01
  isOnline: 1
  maps: {k1 : v1,k2: v2}
  lists:
   - list1
   - list2
  address:
    tel: 15899988899
    name: 上海市

模仿DefaultPropertySourceFactory寫一個yaml資源文件讀取的工廠類:框架

package com.example.springboot.properties.core.propertyResouceFactory;

import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Properties;

/**
 * <pre>
 *  YAML配置文件讀取工廠類
 * </pre>
 * <p>
 * <pre>
 * @author nicky.ma
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2019/11/13 15:44  修改內容:
 * </pre>
 */
public class YamlPropertyResourceFactory implements PropertySourceFactory {

    /**
     * Create a {@link PropertySource} that wraps the given resource.
     *
     * @param name     the name of the property source
     * @param encodedResource the resource (potentially encoded) to wrap
     * @return the new {@link PropertySource} (never {@code null})
     * @throws IOException if resource resolution failed
     */
    @Override
    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource encodedResource) throws IOException {
        String resourceName = Optional.ofNullable(name).orElse(encodedResource.getResource().getFilename());
        if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {//yaml資源文件
            List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, encodedResource.getResource());
            return yamlSources.get(0);
        } else {//返回空的Properties
            return new PropertiesPropertySource(resourceName, new Properties());
        }
    }
}

寫個bean類進行屬性映射,注意換一下factory參數,factory = YamlPropertyResourceFactory.classide

package com.example.springboot.properties.bean;

import com.example.springboot.properties.core.propertyResouceFactory.CommPropertyResourceFactory;
import com.example.springboot.properties.core.propertyResouceFactory.YamlPropertyResourceFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.stereotype.Component;

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

/**
 * <pre>
 *
 * </pre>
 *
 * @author nicky
 * <pre>
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2019年11月03日  修改內容:
 * </pre>
 */
@Component
@PropertySource(value = "classpath:user.yml",encoding = "utf-8",factory = YamlPropertyResourceFactory.class)
@ConfigurationProperties(prefix = "user")
public class User {

    private String userName;
    private boolean isAdmin;
    private Date regTime;
    private Long isOnline;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Address address;

    @Override
    public String toString() {
        return "User{" +
                       "userName='" + userName + '\'' +
                       ", isAdmin=" + isAdmin +
                       ", regTime=" + regTime +
                       ", isOnline=" + isOnline +
                       ", maps=" + maps +
                       ", lists=" + lists +
                       ", address=" + address +
                       '}';
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public boolean isAdmin() {
        return isAdmin;
    }

    public void setAdmin(boolean admin) {
        isAdmin = admin;
    }

    public Date getRegTime() {
        return regTime;
    }

    public void setRegTime(Date regTime) {
        this.regTime = regTime;
    }

    public Long getIsOnline() {
        return isOnline;
    }

    public void setIsOnline(Long isOnline) {
        this.isOnline = isOnline;
    }

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

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

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}
package com.example.springboot.properties.bean;

/**
 * <pre>
 *
 * </pre>
 *
 * @author nicky
 * <pre>
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2019年11月03日  修改內容:
 * </pre>
 */
public class Address {
    private String tel;
    private String name;

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

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    public String getName() {
        return name;
    }

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

junit測試類代碼:

package com.example.springboot.properties;

import com.example.springboot.properties.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootPropertiesConfigApplicationTests {

    @Autowired
    User user;

    @Test
    public void testConfigurationProperties(){
        System.out.println(user);
    }


}
User{userName='root(15899988899)', isAdmin=false, regTime=Fri Nov 01 00:00:00 SGT 2019, isOnline=1, maps={k2=v2, k1=-30363940}, lists=[1f90e323-8a9c-4194-a31c-be9abbe9ce38, a869f68947faa92964d2a36ce86ee980], address=Address{tel='15899988899', name='上海浦東區'}}

若是既要支持原來的yaml,又要支持properties,就能夠將propertyResourceFactory類進行改寫一下:

package com.example.springboot.properties.core.propertyResouceFactory;

import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

/**
 * <pre>
 *  通用的資源文件讀取工廠類
 * </pre>
 * <p>
 * <pre>
 * @author mazq
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2019/11/25 10:35  修改內容:
 * </pre>
 */
public class CommPropertyResourceFactory implements PropertySourceFactory {

    /**
     * Create a {@link PropertySource} that wraps the given resource.
     *
     * @param name     the name of the property source
     * @param resource the resource (potentially encoded) to wrap
     * @return the new {@link PropertySource} (never {@code null})
     * @throws IOException if resource resolution failed
     */
    @Override
    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
        String resourceName = Optional.ofNullable(name).orElse(resource.getResource().getFilename());
        if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {
            List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, resource.getResource());
            return yamlSources.get(0);
        } else {
            return new DefaultPropertySourceFactory().createPropertySource(name, resource);
        }
    }
}

調用的時候,要改一下factory參數

@PropertySource(value = "classpath:user.yml",encoding = "utf-8",factory = CommPropertyResourceFactory.class)

這個類就能夠支持原來的properties文件,也能夠支持yaml文件

User{userName='root(15899988899)', isAdmin=false, regTime=Fri Nov 01 00:00:00 SGT 2019, isOnline=1, maps={k2=v2, k1=-30363940}, lists=[1f90e323-8a9c-4194-a31c-be9abbe9ce38, a869f68947faa92964d2a36ce86ee980], address=Address{tel='15899988899', name='上海浦東區'}}

代碼下載:github下載連接

相關文章
相關標籤/搜索