@PropertySource 注解实现读取 yml 文件

栏目: 编程语言 · XML · 发布时间: 5年前

内容简介:背景: 业务需要创建多个配置类,基于首先我们先来分析一下从源码可以看出,读取资源文件

记一次在开发中使用 @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 方法每次读取一行,然后根据 '='':' 来获取 keyvalue ,而 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 bootymlyaml 对应的加载类为 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

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》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具