SpringBoot系列之@PropertySource讀取yaml文件


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

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

因為yaml語法很簡潔,比較喜歡寫yaml配置文件,很顯然,@PropertySource默認不支持yaml讀取,我們改成@Value注解也是可以讀取的,不過屬性一堆的話,一個一個讀取也是很繁瑣的,通過網上找資料和自己實驗驗證,發現是可以實現對yaml支持

然后,為什么@PropertySource注解默認不支持?可以簡單跟一下源碼

@PropertySource源碼:
在這里插入圖片描述
根據注釋,默認使用DefaultPropertySourceFactory類作為資源文件加載類
在這里插入圖片描述
里面還是調用Spring框架底層的PropertiesLoaderUtils工具類進行讀取的
在這里插入圖片描述
PropertiesLoaderUtils.loadProperties
在這里插入圖片描述
從源碼可以看出也是支持xml文件讀取的,能支持reader就獲取reader對象,否則出件inputStream
在這里插入圖片描述

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

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,這篇博客寫的比較詳細

ok,然后給個例子來實現對yaml配置文件的讀取

# 測試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.class

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下載鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM