内容简介:背景: 业务需要创建多个配置类,基于首先我们先来分析一下从源码可以看出,读取资源文件
记一次在开发中使用 @PropertySource
注解加载 yml
文件
背景: 业务需要创建多个配置类,基于 yml
文件拥有简洁的层次结构,遂配置文件选择 yml
类型。
但在实际的开发中遇到使用 @PropertySource
注解无法加载 yml
配置文件问题。
分析过程:
首先我们先来分析一下 @PropertySource
注解的源码:
public @interface PropertySource { /** 加载资源的名称 */ String name() default ""; /** * 加载资源的路径,可使用classpath,如: * "classpath:/config/test.yml" * 如有多个文件路径放在{}中,使用','号隔开,如: * {"classpath:/config/test1.yml","classpath:/config/test2.yml"} * 除使用classpath外,还可使用文件的地址,如: * "file:/rest/application.properties" */ String[] value(); /** 此属性为根据资源路径找不到文件后是否报错, 默认为是 false */ boolean ignoreResourceNotFound() default false; /** 此为读取文件的编码, 若配置中有中文建议使用 'utf-8' */ String encoding() default ""; /** * 关键:此为读取资源文件的工程类, 默认为: * 'PropertySourceFactory.class' */ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class; } 复制代码
从源码可以看出,读取资源文件 PropertySourceFactory
接口是关键,加下来打开 PropertySourceFactory
接口的源码:
public interface PropertySourceFactory { PropertySource<?> createPropertySource(@Nullable String var1, EncodedResource var2) throws IOException; } 复制代码
发现其中只有一个创建属性资源接口的方法,接下来我们找到实现这个方法的类:
public class DefaultPropertySourceFactory implements PropertySourceFactory { public DefaultPropertySourceFactory() { } public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource); } } 复制代码
在这个类中我们发现其返回了一个对象 ResourcePropertySource
,找到 DefaultPropertySourceFactory
类使用的两个 ResourcePropertySource
类的构造方法:
public ResourcePropertySource(String name, EncodedResource resource) throws IOException { super(name, PropertiesLoaderUtils.loadProperties(resource)); this.resourceName = getNameForResource(resource.getResource()); } public ResourcePropertySource(EncodedResource resource) throws IOException { super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource)); this.resourceName = null; } 复制代码
在上面代码中,两个构造方法都使用了 PropertiesLoaderUtils.loadProperties()
这个属性的方法, 一直点下去, 会发现这么一段代码:
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister) throws IOException { InputStream stream = null; Reader reader = null; try { String filename = resource.getResource().getFilename(); // private static final String XML_FILE_EXTENSION = ".xml"; if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { stream = resource.getInputStream(); persister.loadFromXml(props, stream); } else if (resource.requiresReader()) { reader = resource.getReader(); persister.load(props, reader); } else { stream = resource.getInputStream(); persister.load(props, stream); } } finally { if (stream != null) { stream.close(); } if (reader != null) { reader.close(); } } } 复制代码
由上可知, @PropertySource
注解也可以用来加载 xml
文件,接下来根据 persister.load(props, stream)
方法一直点下去会找到下面一段代码:
private void load0 (LineReader lr) throws IOException { char[] convtBuf = new char[1024]; int limit; int keyLen; int valueStart; char c; boolean hasSep; boolean precedingBackslash; /** * 每次读取一行 */ while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); precedingBackslash = false; /** * 遍历一行字的每一个字符 * 若字符中出现 '='、':'、' '、'\t'、'\f' 则跳出循环 */ while (keyLen < limit) { c = lr.lineBuf[keyLen]; //need check if escaped. // 如果当前遍历字符为 '=' 或 ':' 则跳出循环 if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } // 如果当前遍历字符为 ' ' 或 '\t' 或 '\f' 跳出循环, // 但在接下来的循环中还需要继续遍历知道找到 '=' 或 ':' else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } // 检查是否转义 if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } // 每次循环,keyLen + 1 keyLen++; } /** * 判断valueStart(值的开始下标)是否小于读取行的长度,若小于,则进入循环 */ while (valueStart < limit) { c = lr.lineBuf[valueStart]; // 判断当前字符是否等于空格、制表符、换页符。都不等于则进入循环 if (c != ' ' && c != '\t' && c != '\f') { // 当 hasSep 为false时代表上个 while (keyLen < limit) 循环跳出时c为 空格或制表符或换页符 // 这里继续循环直到找到'='或':'号为止 // 由此可见 在配置文件中'=' 或 ':' 号前可有空格、制表符、换页符 if (!hasSep && (c == '=' || c == ':')) { hasSep = true; } else { break; } } // 每次循环,valueStart + 1 valueStart++; } // 获取配置文件中的key,value并保存 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } } 复制代码
上面 load0
方法每次读取一行,然后根据 '='
或 ':'
来获取 key
和 value
,而 yml
具有鲜明层次结构的特点则不能由此方法读取。
综上分析可知, @PropertySource
注解读取属性文件的关键在于 PropertySourceFactory
接口中的 createPropertySource
方法,所以我们想要实现 @PropertySource
注解读取 yml
文件就需要实现 createPropertySource
方法,在 @PropertySource
注解其是通过 DefaultPropertySourceFactory
类来实现这个方法,我们只需要继承此类,并重写其 createPropertySource
方法即可,实现代码如下:
@Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { if (resource == null){ return super.createPropertySource(name, resource); } List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()); return sources.get(0); } 复制代码
注: spring boot
中 yml
、 yaml
对应的加载类为 YamlPropertySourceLoader
。
测试
@Component @PropertySource(value = "test.yml", encoding = "utf-8", factory = TestFactory.class) @ConfigurationProperties(prefix = "com.test") public class IdCardServerConfig { private String serverCode; ... } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Agile Web Development with Rails 4
Sam Ruby、Dave Thomas、David Heinemeier Hansson / Pragmatic Bookshelf / 2013-10-11 / USD 43.95
Ruby on Rails helps you produce high-quality, beautiful-looking web applications quickly. You concentrate on creating the application, and Rails takes care of the details. Tens of thousands of deve......一起来看看 《Agile Web Development with Rails 4》 这本书的介绍吧!