接口介绍

propertySourceLocator是Spring-cloud-context包下的一个接口,用于定位外部数据源或者内部的自定义数据源配置。Nacos就是通过NacosPropertySourceLocator类实现propertySourceLocator接口,来获取nacos的配置加载到环境中的。下面是接口定义的三个方法。这里我们需要关注locate这个方法。这个方法的实现依赖于我们如何去定义一个新的propertySource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface PropertySourceLocator {
PropertySource<?> locate(Environment environment);

default Collection<PropertySource<?>> locateCollection(Environment environment) {
return locateCollection(this, environment);
}

static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator, Environment environment) {
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
} else if (propertySource instanceof CompositePropertySource) {
Collection<PropertySource<?>> sources = ((CompositePropertySource)propertySource).getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList();
Iterator var5 = sources.iterator();

while(var5.hasNext()) {
PropertySource<?> p = (PropertySource)var5.next();
if (p != null) {
filteredSources.add(p);
}
}

return filteredSources;
} else {
return List.of(propertySource);
}
}
}

项目示例

使用propertySourceLocator接口来加载我们的配置文件主要有三种方式,第一种是在程序启动后,利用Spring的SPI机制,读取META-INF下的spring.factories文件内配置的接口实现类全限定名,来完成实例化。第二种是使用@PropertySource注解来完成,不过使用注解的方式只支持读取properties类型的文件。

自定义属性

首先我们写一个自定义类,实现PropertySourceLocator这个接口,重写locate方法,方法返回值是我们自定义的`PropertySource,之后Spring会自动将其加载到环境内。

1
2
3
4
5
6
7
8
9
10
11
public class SoraPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
// 创建属性下的值,KV格式保存
HashMap<String, Object> envMap = new HashMap<>() {{
put("sora.name", "sora33");
}};
// 这里的sora是我们的PropertySource的属性名
return new MapPropertySource("sora", envMap);
}
}

之后我们要在resources下创建META-INF(java元数据)文件夹,创建一个文件,名字必须为spring.factories,将我们的环境配置加载在主上下文之前,确保后面的主上下文在读取外部配置的时候可以读取到。写入如下(记得把注释删掉,不然会读取不到):

1
2
3
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
# 实现PropertySourceLocator接口的实现类路径
com.sora.config.SoraPropertySourceLocator

BootstrapConfiguration:引导过程阶段,这个阶段会在应用程序的主上下文之前执行,用于初始化一些在主上下文创建前完成的配置,例如服务配置、服务发现等。

写一个测试方法,读取我们刚刚配置好的自定义属性,注意,我们刚刚创建的属性名为sora,Spring会在前面固定加上一个bootstrapProperties-来表示上下文环境。

1
2
3
4
5
6
7
8
9
10
11
// 注入一个接口,获取环境属性
@Resource
private ConfigurableEnvironment configurableEnvironment;


@GetMapping("/test")
public Result readProperties() {
PropertySource<?> propertySource = configurableEnvironment.getPropertySources().get("bootstrapProperties-sora");
logger.info("自定义属性值:[{}]", propertySource.getProperty("sora.name"));
return Result.success("SUCCESS");
}

通过getProperty方法可以看到成功读取到了我们刚才配置的属性。

image-20230622135652530

下面我们继续来说一下读取外部文件,并且是yml格式的。在后面我们还有一个基于注解来读取外部文件,不过使用注解是读取不了yml格式的文件的。

首先我们创建一个yml格式的文件,直接放在resources目录下就可以,文件内容如下

1
2
3
4
5
6
sora:
age: 27
name: sora33
sex:
sakura:
name: ayaneru

image-20230622140507397

之后在方法内我们需要创建一个读取yml文件的对象,指定属性名和文件名,因为返回值是List类型,我们直接获取第一个元素拿到propertySource返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public PropertySource<?> locate(Environment environment) {
final String fileName = "soraInfo.yml";
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
try {
// 指定外部文件的 属性名和文件名
List<PropertySource<?>> propertySources = sourceLoader.load("sora-data-yml",
new ClassPathResource(fileName));
// 获取第一个元素
PropertySource<?> propertySource = propertySources.get(0);
return propertySource;
} catch (IOException e) {
log.info("yml文件未找到!");
}
return null;
}

启动项目,成功读取到了yml文件的数据,也可以通过getProperty方法来获取属性值

image-20230622140839918

读取外部文件

如果我们外部的配置文件格式是properties类型的,可以直接通过使用@PropertySource注解来完成配置的注入。这里我配置classpath为文件名,文件同样放在resources下就可以。属性名为sora-data-properties。文件内容如下

1
2
3
sora.name=sora33
sakura.name=ayaneru
nayuta.type=cute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@AllArgsConstructor
@NoArgsConstructor
@Configuration
@PropertySource(value = "classpath:soraInfo.properties", name = "sora-data-properties")
public class ReadOutFile {

@Value("${sora.name}")
private String soraName;
@Value("${sakura.name}")
private String sakuraName;
@Value("${nayuta.type}")
private String nayutaType;
}

成功读取到配置文件内的属性,需要注意的是,通过注解方式注入的属性名前面不会有bootstrapProperties-的前缀。

image-20230622141447862

原理剖析

我们刚刚说了spring在启动的时候会创建一个bootStrap的ApplicationContext(上下文),它是优先于主应用上下文之前加载的。所以我们可以分为几步来描述其实现原理:

  1. 创建bootStrap上下文:创建上下文环境,准备加载主应用上下文之前的配置
  2. 获取所有的propertySourceLocator:通过Spring的依赖注入机制,获取到所有的propertySourceLocator实现类
  3. 调用locate方法:通过调用locate方法,会获取一个propertySource对象
  4. 添加propertySource到环境内:将每一个locate方法返回的属性对象加入到Environment中,这样,当主应用上下文启动的时候,就可以从Environment获取到所有的配置了。