Spring Boot项目的主类main方法执行了run方法后,整个项目就运行起来了,运行需要的配置肯定也是在run方法执行中加载使用的。Spring Boot官方文档 指出按下图位置去寻找配置文件application.properties或application.yml,序号越小优先级越高,优先级高的文件中的配置会覆盖优先级低的。
当然我们也可以在命令行参数中指定配置文件的位置和名称(不一定非要application):
properties
java -jar myproject.jar --spring.config.name=myproject
arduino
java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
1. 启动流程中的事件监听
下面我们分析一下Spring Boot加载配置文件的源码。SpringApplicatino对象创建时会通过SPI加载事件监听器,我们研究的Spring Boot版本中有一个监听器ConfigFileApplicationListener,它就是负责加载配置文件的。前面说过SpringApplicatino对象执行run方法时,在创建容器前的准备阶段会获先取SpringApplicationRunListener,然后创建并配置环境。SpringApplicationRunListener的接口方法会贯穿run方法执行全流程,在创建并配置环境后,SpringApplicationRunListener的environmentPrepared方法执行:
java
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
//配置文件加载发生在这里
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
//...
}
SpringApplicationRunListener的唯一实现类EventPublishingRunListener执行environmentPrepared时,会构造这个方法对应的事件ApplicationEnvironmentPreparedEvent,然后用它组合的广播器广播:
java
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SimpleApplicationEventMulticaster initialMulticaster;
//...
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
广播方法multicastEvent:
java
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
//...
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
//...
}
重载的multicastEvent方法会调用getApplicationListeners,这个方法会根据事件对象和事件类型去搜索对应的所有事件监听器:
获取到监听器后遍历,分别执行invokeListener方法,invokeListener会再调doInvokeListener:
2. 加载配置文件的监听器:ConfigFileApplicationListener
ConfigFileApplicationListener负责加载配置文件,遍历到它时会进入它的事件监听方法onApplicationEvent:
2.1 事件监听方法
当前事件是ApplicationEnvironmentPreparedEvent,所以会继续调用onApplicationEnvironmentPreparedEvent那个分支:
java
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final Set<String> LOAD_FILTERED_PROPERTY;
//...
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
//SPI加载所有EnvironmentPostProcessor
List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
//ConfigFileApplicationListener也是EnvironmentPostProcessor的实现类
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
Iterator var3 = postProcessors.iterator();
while(var3.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
//调用每个EnvironmentPostProcessor后置处理方法
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, this.getClass().getClassLoader());
}
}
onApplicationEnvironmentPreparedEvent会获取所有的环境后置处理器然后逐个调用对应的后置处理方法。先用SPI加载META-INF/spring.factories中配置的,再把ConfigFileApplicationListene本身也加入,因为它也实现了EnvironmentPostProcessor。
具体调用ConfigFileApplicationListener的postProcessEnvironment:
java
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
this.addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
(new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
}
最后创建ConfigFileApplicationListener的内部类Loader对象并调用它的load方法,这里就是加载配置文件具体逻辑。
2.2 加载配置文件
Loader类:
java
private class Loader {
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
//...
//构造器中用SPI加载了配置的PropertySourceLoader
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, this.getClass().getClassLoader());
}
void load() {
FilteredPropertySource.apply(this.environment, "defaultProperties", ConfigFileApplicationListener.LOAD_FILTERED_PROPERTY, (defaultProperties) -> {
this.profiles = new LinkedList();
this.processedProfiles = new LinkedList();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap();
this.initializeProfiles();
while(!this.profiles.isEmpty()) {
ConfigFileApplicationListener.Profile profile = (ConfigFileApplicationListener.Profile)this.profiles.poll();
if (this.isDefaultProfile(profile)) {
this.addProfileToEnvironment(profile.getName());
}
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
this.load((ConfigFileApplicationListener.Profile)null, this::getNegativeProfileFilter, this.addToLoaded(MutablePropertySources::addFirst, true));
this.addLoadedPropertySources();
this.applyActiveProfiles(defaultProperties);
});
}
//...
2.2.1 FilteredPropertySource的apply方法
内部类Loader的load方法调用FilteredPropertySource的apply方法。apply方法的参数分别是从前面事件对象获取的environment、propertySource名称"defaultProperties"、Set集合LOAD_FILTERED_PROPERTY和函数式接口Consumer<PropertySource<?>>,Consumer接受一个参数进行处理返回void。apply方法是对环境中的某属性进行处理,如果能获取到则做替换然后执行Consumer的逻辑,否则直接执行Consumer。
java
class FilteredPropertySource extends PropertySource<PropertySource<?>> {
//...
static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties, Consumer<PropertySource<?>> operation) {
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource<?> original = propertySources.get(propertySourceName);
if (original == null) {
operation.accept((Object)null);
} else {
propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
try {
operation.accept(original);
} finally {
propertySources.replace(propertySourceName, original);
}
}
}
}
debug发现:
从环境中获取属性defaultProperties,没有获取到,就不做额外处理,直接把第四个参数那个函数式接口的逻辑拿来执行。
2.2.2 load方法的参数
在这里,FilteredPropertySource的apply方法会直接执行Consumer接口的逻辑,会执行到下面的地方:
重点看重载的load方法:
java
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
this.getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
this.load(location, name, profile, filterFactory, consumer);
});
});
}
第一个参数Profile和环境有关。第二个参数DocumentFilterFactory是个接口,唯一的接口方法会根据profile返回DocumentFilter:
java
@FunctionalInterface
private interface DocumentFilterFactory {
ConfigFileApplicationListener.DocumentFilter getDocumentFilter(ConfigFileApplicationListener.Profile profile);
}
具体传参时要传入接口实现类对象,这里采用匿名内部类创建对象,创建对象时必须覆盖接口方法getDocumentFilter。而覆盖的getDocumentFilter的方法体如果只有一行return语句,可以使用方法引用。可以举例复习一下这种语法:
java
public interface I {
Integer getVal(String str);
}
public class Client {
public static void main(String[] args) {
I i = new I() {
@Override
public Integer getVal(String str) {
return Integer.valueOf(str);
}
};
I i2 = Integer::valueOf;
System.out.println("i.getVal(\"123\") = " + i.getVal("123"));
System.out.println("i2.getVal(\"12\") = " + i2.getVal("12"));
}
}
本地运行是可以的。
源码采用this::getPositiveProfileFilter:
java
private ConfigFileApplicationListener.DocumentFilter getPositiveProfileFilter(ConfigFileApplicationListener.Profile profile) {
return (document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
} else {
return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
}
};
}
getPositiveProfileFilter接受profile并返回DocumentFilter,而DocumentFilter也是一个接口:
java
@FunctionalInterface
private interface DocumentFilter {
boolean match(ConfigFileApplicationListener.Document document);
}
所以getPositiveProfileFilter返回时也要创建DocumentFilter实现类对象,也是匿名内部类对象,然后要覆盖match方法,由于这里的逻辑不只是一行语句,所以用了 <math xmlns="http://www.w3.org/1998/Math/MathML"> l a m b d a lambda </math>lambda表达式。这里的逻辑是过滤文档Document是否符合某种属性,文档是对配置文件的一种抽象:
java
private static class Document {
private final PropertySource<?> propertySource;//和配置有关
private String[] profiles;
private final Set<ConfigFileApplicationListener.Profile> activeProfiles;
private final Set<ConfigFileApplicationListener.Profile> includeProfiles;
//...
}
再来看load方法的第三个参数DocumentConsumer,它也是一个接口,只是要处理profile和document,不返回参数:
java
@FunctionalInterface
private interface DocumentConsumer {
void accept(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.Document document);
}
源码用了this.addToLoaded(MutablePropertySources::addLast, false),这个方法也是同样的逻辑,返回一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> l a m b d a lambda </math>lambda 表达式:
java
private Map<ConfigFileApplicationListener.Profile, MutablePropertySources> loaded;
private ConfigFileApplicationListener.DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
Iterator var5 = this.loaded.values().iterator();
while(var5.hasNext()) {
MutablePropertySources merged = (MutablePropertySources)var5.next();
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
MutablePropertySources mergedx = (MutablePropertySources)this.loaded.computeIfAbsent(profile, (k) -> {
return new MutablePropertySources();
});
addMethod.accept(mergedx, document.getPropertySource());
};
}
先来看看addToLoaded的第一个参数BiConsumer,它是函数式接口,还是同样的套路!匿名内部类创建的对象要覆盖BiConsumer的accept方法,对MutablePropertySources类型参数和 PropertySource<?>类型参数加逻辑而不返回参数,又是一个可以采用方法引用的地方,所以源码用了MutablePropertySources::addLast,只对PropertySource类型参数处理了,MutablePropertySources类型参数弃之不理:
java
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList;
//...
public void addLast(PropertySource<?> propertySource) {
synchronized(this.propertySourceList) {
this.removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
}
//...
}
回头看this.addToLoaded(MutablePropertySources::addLast, false),checkForExisting是false,所以先根据profile获取一个属性源MutablePropertySources,然后从document获取propertySource,最后把这两个参数交给BiConsumer处理。从MutablePropertySources::addLast可知,这个addToLoaded方法其实就是要把从document得到的propertySource重新加入到属性列表propertySourceList中去,也就是从配置文件读配置然后维护到属性集中去。有点绕,花点时间debug一下就明白了。
2.2.3 load方法
返回看load的具体逻辑:
java
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
//getSearchLocations调用后,再执行两重循环
this.getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
this.load(location, name, profile, filterFactory, consumer);
});
});
}
先调getSearchLocations确定配置文件位置:
java
private Set<String> getSearchLocations() {
//从environment中获取spring.config.additional-location
//这是配置文件的默认加载路径
Set<String> locations = this.getSearchLocations("spring.config.additional-location");
//如果指定了spring.config.location就添加它指定的路径
if (this.environment.containsProperty("spring.config.location")) {
locations.addAll(this.getSearchLocations("spring.config.location"));
} else {
//如果没有就添加这几个写死的路径返回
locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/"));
}
return locations;
}
//用逗号分割字符串形成集合并排序
private Set<String> asResolvedSet(String value, String fallback) {
List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(value != null ? this.environment.resolvePlaceholders(value) : fallback)));
Collections.reverse(list);
return new LinkedHashSet(list);
}
获取文件位置后对每一个位置的文件处理,这里有双重循环,第一层确定配置文件名称:
java
private Set<String> getSearchNames() {
//如果参数配置了spring.config.name就使用,否则用默认的"application"
if (this.environment.containsProperty("spring.config.name")) {
String property = this.environment.getProperty("spring.config.name");
Set<String> names = this.asResolvedSet(property, (String)null);
names.forEach(this::assertValidConfigName);
return names;
} else {
return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
}
}
第二重会遍历每一个配置文件,调用重载的load方法,参数location是目录名,name是文件名,其余几个都是之前传来的参数:
java
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
//if一般不走,除非配了spring.config.name但配的是空
if (!StringUtils.hasText(name)) {
Iterator var13 = this.propertySourceLoaders.iterator();
PropertySourceLoader loader;
do {
//...
loader = (PropertySourceLoader)var13.next();
} while(!this.canLoadFileExtension(loader, location));
this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
} else {
Set<String> processed = new HashSet();
//获取SPI获取的所有PropertySourceLoader
Iterator var7 = this.propertySourceLoaders.iterator();
//遍历所有的PropertySourceLoader
while(var7.hasNext()) {
PropertySourceLoader loaderx = (PropertySourceLoader)var7.next();
String[] var9 = loaderx.getFileExtensions();
int var10 = var9.length;
//每一个PropertySourceLoader指定的配置文件扩展名继续遍历
for(int var11 = 0; var11 < var10; ++var11) {
String fileExtension = var9[var11];
if (processed.add(fileExtension)) {
//处理文件名称,(location + name) 会生成像 classpath:/application
this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
}
}
}
}
用SPI加载所有PropertySourceLoader然后遍历,而PropertySourceLoader会指定文件扩展名,并真正加载解析这种文件。默认SPI获取PropertiesPropertySourceLoader和YamlPropertySourceLoader,PropertiesPropertySourceLoader可以加载扩展名是properties和
xml的文件,YamlPropertySourceLoader可以加载扩展名yml和yaml的文件。如果想加载json格式文件自定义PropertySourceLoader实现加载json文件的逻辑,然后配置到项目META-INF/spring.factories文件中去即可。
这里有处理文件名称的方法loadForFileExtension,它会得到配置文件全名然后继续调重载load方法:
java
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile,
ConfigFileApplicationListener.DocumentFilterFactory filterFactory,
ConfigFileApplicationListener.DocumentConsumer consumer) {
//文档过滤器工厂中取出过滤器
ConfigFileApplicationListener.DocumentFilter defaultFilter = filterFactory.getDocumentFilter((ConfigFileApplicationListener.Profile)null);
ConfigFileApplicationListener.DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
//加载不带profile的配置文件
if (profile != null) {
//具体文件名,像classpath:/application-dev.properties这样
String profileSpecificFile = prefix + "-" + profile + fileExtension;
this.load(loader, profileSpecificFile, profile, defaultFilter, consumer);
this.load(loader, profileSpecificFile, profile, profileFilter, consumer);
Iterator var10 = this.processedProfiles.iterator();
//加载所有profile对应的配置文件
while(var10.hasNext()) {
ConfigFileApplicationListener.Profile processedProfile = (ConfigFileApplicationListener.Profile)var10.next();
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
this.load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
//加载不带profile的配置文件
this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
最终每个文件都会调这个终级的load重载方法:
java
private void load(PropertySourceLoader loader, String location,
ConfigFileApplicationListener.Profile profile,
ConfigFileApplicationListener.DocumentFilter filter,
ConfigFileApplicationListener.DocumentConsumer consumer) {
//调用Resource类到指定路径
Resource[] resources = this.getResources(location);
Resource[] var7 = resources;
int var8 = resources.length;
for(int var9 = 0; var9 < var8; ++var9) {
Resource resource = var7[var9];
try {
StringBuilder description;
if (resource != null && resource.exists()) {
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
//...
} else if (resource.isFile() && this.isPatternLocation(location) && this.hasHiddenPathElement(resource)) {
//...
} else {
String name = "applicationConfig: [" + this.getLocationName(location, resource) + "]";
//读取配置文件内容,封装成文档类
List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
//...
} else {
List<ConfigFileApplicationListener.Document> loaded = new ArrayList();
Iterator var14 = documents.iterator();
while(var14.hasNext()) {
ConfigFileApplicationListener.Document document = (ConfigFileApplicationListener.Document)var14.next();
//filter参数用来过滤获取的文档是否满足profile
if (filter.match(document)) {
//将配置文件中配置的spring.profiles.active和
//spring.profiles.include的值写入集合profiles中,
//上层调用方法会读取profiles集合中的值,并读取对应的配置文件
this.addActiveProfiles(document.getActiveProfiles());
this.addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((documentx) -> {
//consumer接口处理
consumer.accept(profile, documentx);
});
//...
}
}
}
}
//...
}
}
读取配置文件内容,封装成文档类,如果文档满足profile条件就加入集合loaded。这里回头看第一个说到的getPositiveProfileFilter方法,它是用profile做条件的:
java
private ConfigFileApplicationListener.DocumentFilter getPositiveProfileFilter(ConfigFileApplicationListener.Profile profile) {
return (document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
} else {
return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
}
};
}
loadDocuments读取配置文件内容,是用前面SPI加载的PropertySourceLoader实现的:
java
private List<ConfigFileApplicationListener.Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
ConfigFileApplicationListener.DocumentsCacheKey cacheKey = new ConfigFileApplicationListener.DocumentsCacheKey(loader, resource);
List<ConfigFileApplicationListener.Document> documents = (List)this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
//据不同的PropertySourceLoader调用相应的load方法
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = this.asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
以PropertySourceLoader为例:
java
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
private static final String XML_FILE_EXTENSION = ".xml";
public String[] getFileExtensions() {
return new String[]{"properties", "xml"};
}
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = this.loadProperties(resource);
return properties.isEmpty() ? Collections.emptyList() : Collections.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
//OriginTrackedPropertiesLoader的load方法会循环读取properties文件每行,每行用"="分割获取属性名和属性值
return (Map)(filename != null && filename.endsWith(".xml") ? PropertiesLoaderUtils.loadProperties(resource) : (new OriginTrackedPropertiesLoader(resource)).load());
}
}
PropertySourceLoader加载好配置文件后再设置回文档类,文档会交给consumer接口处理,就就是会回到前面说到l的this.addToLoaded(MutablePropertySources::addLast, false)中执行,逻辑闭环了。
3 加载JSON格式配置文件
分析完源码,我们可以来实现一下加载json配置文件,只要自定义PropertySourceLoader来实现加载json文件的逻辑,然后配到spring.factories文件。
1.定义普通类
java
public class Developer {
@Value("${dev.skill}")
private String skill;
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
2.定义application.json,并注释掉application.properties文件中相同配置
json
{
"server.port": 9001,
"dev.skill": "Java"
}
3.定义PropertySourceLoader实现类
java
package org.cosmos.springboot.induction.prop;
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[]{"json"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
ReadableByteChannel channel = resource.readableChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer);
String content = new String(buffer.array());
JSONObject jsonObject = com.alibaba.fastjson.JSON.parseObject(content);
Map<String, Object> map = new HashMap<>(jsonObject.size());
for (String key : jsonObject.keySet()) {
map.put(key, jsonObject.getString(key));
}
return Collections.singletonList(new MapPropertySource("jsonSource", map));
}
}
4.META-INF/spring.factories中添加
org.springframework.boot.env.PropertySourceLoader=org.cosmos.springboot.induction.prop.JsonPropertySourceLoader
5.定义主启动类并运行
java
@SpringBootApplication
public class PropSpringApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(PropSpringApplication.class, args);
Developer developer = ctx.getBean(Developer.class);
System.out.println("获取到Bean为:" + developer + ",属性skill值:" + developer.getSkill());
}
@Bean
public Developer develop(){
return new Developer();
}
}
结果:
4. 总结
Spring Boot在启动流程中加载事件监听器ConfigFileApplicationListener并调用它的监听方法,监听方法会用SPI查找所有PropertySourceLoader并通过它们完成不同格式的文件加载,这也是一个扩展点,我们可以自定义加载其他格式的文件。