当前位置: 首页 > article >正文

SpringBoot之OriginTrackedPropertiesLoader类源码学习

源码解析

/**
 * 作用是从给定的资源(如文件或输入流)中加载 .properties 文件,
 * 并将属性键值对转换为带有来源信息(origin)的 OriginTrackedValue 对象。
 */
public class OriginTrackedPropertiesLoader {

	private final Resource resource;

	/**
	 * Create a new {@link OriginTrackedPropertiesLoader} instance.
	 * @param resource the resource of the {@code .properties} data
	 */
	public OriginTrackedPropertiesLoader(Resource resource) {
		Assert.notNull(resource, "Resource must not be null");
		this.resource = resource;
	}

	/**
	 * Load {@code .properties} data and return a map of {@code String} ->
	 * {@link OriginTrackedValue}.
	 * @return the loaded properties
	 * @throws IOException on read error
	 */
	public Map<String, OriginTrackedValue> load() throws IOException {
		return load(true);
	}

	/**
	 * 加载配置数据到一个Map中,根据指定的资源文件。
	 * 如果expandLists为true,则将列表类型的配置项展开。
	 *
	 * @param expandLists 是否展开列表类型的配置项
	 * @return 包含配置数据的Map,键为配置项名称,值为配置项的值和来源信息
	 * @throws IOException 如果读取资源文件时发生错误
	 */
	Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
		//创建CharacterReader对象,用于逐行读取资源文件内容。
		try (CharacterReader reader = new CharacterReader(this.resource)) {
			//初始化结果Map
			Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
			//初始化字符串缓冲区
			StringBuilder buffer = new StringBuilder();
			//循环读取逐行资源文件内容,直到文件末尾
			while (reader.read()) {
				//读取配置项的键名,并去除前后空格
				String key = loadKey(buffer, reader).trim();
				//如果配置项是列表类型且需要展开列表,则处理列表配置项
				if (expandLists && key.endsWith("[]")) {
					//去除列表配置项的"[]"后缀
					key = key.substring(0, key.length() - 2);
					//初始化列表索引
					int index = 0;
					//循环读取列表中的每个配置项
					do {
						//读取配置项的值
						OriginTrackedValue value = loadValue(buffer, reader, true);
						//将配置项添加到结果Map中,键名格式为key[index]
						put(result, key + "[" + (index++) + "]", value);
						//如果当前行不是行尾,则继续读取
						if (!reader.isEndOfLine()) {
							reader.read();
						}
					} while (!reader.isEndOfLine());
				}
				else {
					//读取非列表配置项的值
					OriginTrackedValue value = loadValue(buffer, reader, false);
					//将配置项添加到结果Map中
					put(result, key, value);
				}
			}
			//返回包含所有配置项的Map
			return result;
		}
	}

	/**
	 * 从输入中加载属性键
	 * 该方法负责读取输入直到遇到属性分隔符或行尾,同时忽略前导和尾随的空白字符
	 *
	 * @param buffer 用于存储读取的键的字符串构建器
	 * @param reader 提供输入字符的字符阅读器
	 * @return 返回读取的属性键的字符串表示
	 * @throws IOException 如果读取过程中发生I/O错误
	 */
	private String loadKey(StringBuilder buffer, CharacterReader reader) throws IOException {
		// 清空缓冲区以准备读取新的键
		buffer.setLength(0);
		// 初始化前一个字符是否为空白字符的标志
		boolean previousWhitespace = false;
		// 循环读取直到行尾
		while (!reader.isEndOfLine()) {
			// 如果遇到属性分隔符,读取下一个字符并返回当前缓冲区的内容
			if (reader.isPropertyDelimiter()) {
				reader.read();
				return buffer.toString();
			}
			// 如果当前字符不是空白字符,但前一个字符是空白字符,则返回当前缓冲区的内容
			if (!reader.isWhiteSpace() && previousWhitespace) {
				return buffer.toString();
			}
			// 更新前一个字符是否为空白字符的标志
			previousWhitespace = reader.isWhiteSpace();
			// 将当前字符追加到缓冲区中
			buffer.append(reader.getCharacter());
			// 读取下一个字符
			reader.read();
		}
		// 如果到达行尾,返回当前缓冲区的内容
		return buffer.toString();
	}


	/**
	 * 从输入中加载一行值,可以选择性地在列表分隔符处分割
	 *
	 * @param buffer 缓存区,用于存储从输入中读取的值
	 * @param reader 字符读取器,用于从输入源读取字符
	 * @param splitLists 指示是否应在遇到列表分隔符时分割的布尔值
	 * @return 返回一个包含值及其来源信息的OriginTrackedValue对象
	 * @throws IOException 如果在读取过程中发生I/O错误
	 */
	private OriginTrackedValue loadValue(StringBuilder buffer, CharacterReader reader, boolean splitLists)
			throws IOException {
		// 清空缓冲区以准备读取新的值
		buffer.setLength(0);
		// 跳过行首的空白字符,直到遇到非空白字符或行尾
		while (reader.isWhiteSpace() && !reader.isEndOfLine()) {
			reader.read();
		}
		// 记录当前读取位置,用于后续创建Origin对象
		Location location = reader.getLocation();
		// 读取字符直到行尾或(如果splitLists为真)遇到列表分隔符
		while (!reader.isEndOfLine() && !(splitLists && reader.isListDelimiter())) {
			buffer.append(reader.getCharacter());
			reader.read();
		}
		// 创建一个表示值来源的Origin对象
		Origin origin = new TextResourceOrigin(this.resource, location);
		// 使用缓冲区中的字符串和其来源信息创建并返回一个OriginTrackedValue对象
		return OriginTrackedValue.of(buffer.toString(), origin);
	}


	/**
	 * Reads characters from the source resource, taking care of skipping comments,
	 * handling multi-line values and tracking {@code '\'} escapes.
	 * 用于逐字符读取文件内容,处理注释、转义字符、多行值等特殊情况。
	 * 提供了多种辅助方法来跳过空白字符、处理注释、读取转义字符等。
	 */
	private static class CharacterReader implements Closeable{
	//省略......
	}

}

案例

test-properties.properties配置文件

   # foo
blah   =   hello world
bar   foo=baz
hello   world
proper\\ty=test
foo
bat = a\\
bling = a=b

#commented-property=test
test=properties
test-unicode=properties\u0026test
  # comment ending \
test\=property=helloworld
test-colon-separator: my-property
test-tab-property=foo\tbar
test-return-property=foo\rbar
test-newline-property=foo\nbar
test-form-feed-property=foo\fbar
test-whitespace-property   =   foo   bar
test-multiline= a\
  b\\\
  c
foods[]=Apple,\
Orange,\
Strawberry,\
Mango
languages[perl]=Elite
languages[python]=Elite
language[pascal]=Lame
test-multiline-immediate=\
foo
!commented-two=bang\
test-bang-property=foo!
another=bar
test-property-value-comment=foo \
!bar #foo
test-multiline-immediate-bang=\
!foo

#test ISO 8859-1
test-iso8859-1-chars=����������

test-with-trailing-space= trailing 
	private ClassPathResource resource;

	private Map<String, OriginTrackedValue> properties;
	
	@Test
	void compareToJavaProperties() throws Exception {
		String path = "test-properties.properties";
		this.resource = new ClassPathResource(path, getClass());
		this.properties = new OriginTrackedPropertiesLoader(this.resource).load();
		Properties java = PropertiesLoaderUtils.loadProperties(this.resource);
		Properties ours = new Properties();
		new OriginTrackedPropertiesLoader(this.resource)
				.load(false)
				.forEach((k, v) -> System.out.println(k+":"+v.getValue()));
	}

运行结果

blah:hello world
bar:foo=baz
hello:world
proper\ty:test
foo:
bat:a\
bling:a=b
test:properties
test-unicode:properties&test
test=property:helloworld
test-colon-separator:my-property
test-tab-property:foo	bar
bar
test-newline-property:foo
bar
test-form-feed-property:foobar
test-whitespace-property:foo   bar
test-multiline:ab\c
foods[]:Apple,Orange,Strawberry,Mango
languages[perl]:Elite
languages[python]:Elite
language[pascal]:Lame
test-multiline-immediate:foo
test-bang-property:foo!
another:bar
test-property-value-comment:foo !bar #foo
test-multiline-immediate-bang:!foo
test-iso8859-1-chars:����������
test-with-trailing-space:trailing 

http://www.kler.cn/a/504105.html

相关文章:

  • HTTP/HTTPS ⑤-CA证书 || 中间人攻击 || SSL/TLS
  • unity——Preject3——面板基类
  • 蓝牙BT04-A的使用与相关AT指令
  • v-bind操作class
  • Hadoop•安装JDK
  • 龙蜥Linux系统部署docker21.1.3版本
  • 网管平台(进阶篇):路由器的管理实践
  • 华三S6520交换机配置console和ssh
  • 【数据结构学习笔记】19:跳表(Skip List)
  • 浅谈计算机网络02 | SDN控制平面
  • 一个使用 Golang 编写的新一代网络爬虫框架,支持JS动态内容爬取
  • 【漫话机器学习系列】047.指数型线性单元(Exponential Linear Units,ELU)
  • 1.4 给应用添加service,执行扩容和滚动更新
  • TDSQL 内存占用解析一例
  • Golang|单机并发缓存
  • 24. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--预算扣除、退回、补充
  • 华为2024嵌入式研发面试题
  • Adobe与MIT推出自回归实时视频生成技术CausVid。AI可以边生成视频边实时播放!
  • Oracle 终止正在执行的SQL
  • 下载导出Tomcat上的excle文档,浏览器上显示下载
  • Web前端------HTML块级和行内标签之块级标签
  • kube-prometheus监控Linux主机
  • 关于H5复制ios没有效果
  • JavaScript系列(25)--性能优化技术详解
  • 如何通过NMudbus读取寄存器数据
  • Vue环境变量配置指南:如何在开发、生产和测试中设置环境变量