在之前有介绍过借助注解 @PropertySource
来引入自定义的配置文件,在当时遇到抛出了一个问题,通过这个注解可以正确获取到 .properties
文件的配置信息,但是 yaml
文件却读取不到,最近又碰到这个问题,正好把之前挖的坑填上;本文将主要定位一下,为啥yml文件读取不了,又可以如何处理
如对之前博文有兴趣的小伙伴,可以查看: 180921-SpringBoot基础篇配置信息之自定义配置指定与配置内引用
I. 项目环境
1. 基本配置
本文后续的源码定位以及实例演示都是基于 SpringBoot 2.2.1.RELEASE
进行,如需复现本文中的case,请确保环境一致
- IDEA
- MAVEN
- SpringBoot 2.2.1.RELEASE
- JDK1.8
2. 实例项目
创建一个SpringBoot项目,用于后续的演示,首先创建一个配置文件 biz.properties
biz.token=mytoken biz.appKey=asdf biz.appVersion=1 biz.source=xxx.yyy biz.uuid=${biz.token}#${biz.appKey}
接下来定义对应的配置类
@Data @Configuration @PropertySource({"classpath:biz.properties"}) @ConfigurationProperties(prefix = "biz") public class OtherProperBean { private String token; private String appKey; private Integer appVersion; private String source; private String uuid; }
最后补上SpringBoot项目不可获取的启动类
/** * Created by @author yihui in 14:08 18/9/19. */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } }
II. PropertySource原理分析
想要定位为啥 @PropertySource
注解只会获取到 properties
文件的配置,而不能获取 yaml
文件配置信息,最直接的办法当然是直接撸源码(实际上最简单的办法直接借助搜索引擎,看一下有没有哪位大佬有过相关分享,如果不是为了写本文,我可是完全没想开撸,毕竟从提出这个问题到现在回复,也过了两年多了:sob:…)
1. 源码定位
那么这个源码可以怎么定位分析呢,先直接进入这个注解瞅一下
public @interface PropertySource { // ... 省略无关的属性 trueClass<? extends PropertySourceFactory> factory() default PropertySourceFactory.class; }
请注意上面的特意留出来的 PropertySourceFactory
, 从命名上来看,大致就能感觉这个工厂类与属性有关了,主要就是为了创建 PropertySource
对象
它就比较有意思了,如果没有猜错的话,配置文件加载到Spring容器之后,多半就会与 PropertySource
关联起来了(所以说好的命名可以省很多注释说明)
接下来看一下这个工厂类的默认实现 DefaultPropertySourceFactory
,源码很简单
public class DefaultPropertySourceFactory implements PropertySourceFactory { true@Override truepublic PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { truetruereturn (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource)); true} }
在这里我们打个断点,确认一下会发生什么神器的事情
从上面的截图可以看到,这个 EncodedResource
包含了我们指定的配置文件,直接单步进去,可以看到执行的时候下面这个
// org.springframework.core.io.support.ResourcePropertySource#ResourcePropertySource(org.springframework.core.io.support.EncodedResource) public ResourcePropertySource(EncodedResource resource) throws IOException { truetruesuper(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource)); truetruethis.resourceName = null; }
请注意,核心代码不是 super()
这个构造方法,而是传参的 PropertiesLoaderUtils.loadProperties(resource)
上面这一行调用,就是实现具体的从配置文件中获取配置信息
下面是具体的实现(摘抄有用的部分逻辑)
// org.springframework.core.io.support.PropertiesLoaderUtils public static Properties loadProperties(EncodedResource resource) throws IOException { trueProperties props = new Properties(); truefillProperties(props, resource); truereturn props; } public static void fillProperties(Properties props, EncodedResource resource) throws IOException { // 属性填充,注意DefaultPropertiesPersister truefillProperties(props, resource, new DefaultPropertiesPersister()); } static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister) throws IOException { ... truetry { truetrueString filename = resource.getResource().getFilename(); truetrueif (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { truetruetruestream = resource.getInputStream(); truetruetrue// 这个是关键 truetruetruepersister.loadFromXml(props, stream); truetrue} truetrueelse if (resource.requiresReader()) { truetruetruereader = resource.getReader(); truetruetrue// 关键调用 truetruetruepersister.load(props, reader); truetrue} truetrueelse { truetruetruestream = resource.getInputStream(); truetruetrue// 关键调用 truetruetruepersister.load(props, stream); truetrue} true} true... }
配置信息的读取,最终依靠的就是 org.springframework.util.DefaultPropertiesPersister#load()
,到这里我们基本上就找到了从配置文件中读取配置的“幕后黑手”,直接看一下它的实现逻辑就能知道为啥不支持yaml了
public class DefaultPropertiesPersister implements PropertiesPersister { true@Override truepublic void load(Properties props, InputStream is) throws IOException { truetrueprops.load(is); true} true@Override truepublic void load(Properties props, Reader reader) throws IOException { truetrueprops.load(reader); true} }
直接进入看到源码,非常简单直观的实现方式了,直接使用jdk的 java.util.Properties#load(java.io.InputStream)
来读取配置文件,所以真相已经大白了(原来都是jdk的锅:joy:)
2. yaml文件支持
经过上面的一番操作,我们知道 @ConfigurationProperties
加载配置文件,主要是借助jdk的 Properties#load
方法来读取配置文件到容器内,那么若我们希望加载yaml配置文件,可以怎么搞呢?
因为SpringBoot是支持yaml配置文件的读取的,所以我们完全可以扩展一下,借助SpringBoot的工具类来实现配置文件加载,所以可以实现自定义的 PropertySourceFactory
public class YamlSourceFactory extends DefaultPropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { if (resource == null) { return super.createPropertySource(name, resource); } // 这里使用Yaml配置加载类来读取yml文件信息 List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()); return sources.get(0); } }
然后再我们希望使用的地方,利用自定义的工厂类替换默认的即可
@Data @Configuration @PropertySource(value = {"classpath:biz2.yml"}, factory = YamlSourceFactory.class) @ConfigurationProperties(prefix = "biz2.yml") public class YmlProperties { private Integer type; private String name; private List<Map<String, String>> ary; }
对应的配置文件如下
biz2: yml: type: 1 name: biz.yml.name ary: - a: hello - b: world
最后实例验证一下
@SpringBootApplication public class Application { public Application(YmlProperties ymlProperties) { System.out.println(ymlProperties); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
3. 小结
当我们希望加载自定义的配置文件时, @PropertySource
注解是一个非常好的选择(当然也可以借助多环境配置方案,指定 spring.profiles.active
的值,实现加载前缀为 application-
的配置文件,有兴趣的小伙伴可以查看我之前的博文)
请注意 @PropertySource
引入的配置文件不支持 yaml
文件,如需支持,可以参考本文中的实现方式,自定义一个yaml文件的 PropertySourceFactory
最后提一句,遇到问题千万不要放过,尽量迅速解决,不要留待以后,不然拖延症发作的话,这个时间可能就一直悬着了…
III. 其他
0. 项目
项目源码
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/000-properties
系列博文
- 【基础系列】实现一个自定义配置加载器(应用篇)
- 【基础系列】SpringBoot配置信息之默认配置
- 【基础系列】SpringBoot配置信息之配置刷新
- 【基础系列】SpringBoot基础篇配置信息之自定义配置指定与配置内引用
- 【基础系列】SpringBoot基础篇配置信息之多环境配置信息
- 【基础系列】SpringBoot基础篇配置信息之如何读取配置信息
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
打赏 如果觉得我的文章对您有帮助,请随意打赏。