接口介绍

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 获取到所有的配置了。