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.class
ide
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下载连接