Environment
PropertySourceLocator
其实从上面的Nacos代码处我们也可以知道PropertySourceLocator的作用是什么:它的作用就是用来按照我们自定义的方式来加载配置源PropertySource。
我们知道,我们的项目的配置文件一般都是:properties、yml、yaml、xml这些配置文件类型。
如果我想要用json做配置文件,那么springboot是不会默认帮助我们加载的。
上面的配置文件,如果我不自己编写代码去加载和解析,那么项目启动的时候,我是得不到myname的值的。
因此这个时候,我们就需要用到PropertySourceLocator。
PropertySourceLocator的作用主要体现在如下几个地方:
- 定位配置源: ○ PropertySourceLocator 用于定位配置源,这些配置源可以是本地文件、环境变量、远程配置中心等。
- 加载配置数据: ○ 一旦定位到配置源,该接口用于从这些源中加载配置数据。
- 配置注入: ○ 加载的配置数据可以注入到 Spring 的 Environment 中,以便应用程序可以使用标准方式访问这些配置(例如使用 @Value、@ConfigurationProperties 注解)。 使用方法也比较简单
- 创建实现类: ○ 创建一个类实现 PropertySourceLocator 接口。
- 实现 locate 方法: ○ 在 locate 方法中编写逻辑来定位并加载配置源。 ○ 通常,这将涉及连接远程配置中心(如果适用),获取配置数据,并将其封装为 PropertySource 对象。
- 注册为 Spring Bean: ○ 将实现类注册为 Spring Bean,以便在应用启动时由 Spring 容器管理。
- 使用Environment对象: ○ 在需要时,通过 Environment 对象访问加载的配置数据。 这里为了能加载刚才的json配置。
package blossom.project.config.core.configuration;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Properties;
import java.io.InputStream;
public class JsonFilePropertySourceLocator implements PropertySourceLocator {
private static final String JSON_FILE_LOCATION = "application.json"; // JSON 文件路径
@Override
public PropertySource<?> locate(Environment environment) {
try {
Resource resource = new ClassPathResource(JSON_FILE_LOCATION);
if (resource.exists()) {
ObjectMapper mapper = new ObjectMapper();
InputStream is = resource.getInputStream();
// 将 JSON 文件内容转换为 Properties
Properties properties = mapper.readValue(is, Properties.class);
return new PropertiesPropertySource("jsonPropertySource", properties);
}
} catch (Exception e) {
throw new RuntimeException("Failed to load json properties", e);
}
return null;
}
}
这里当初还闹了一个乌龙,这里请注意好注释的代码和没有注释的代码的区别。
使用注释的那种是不能加载到我们的Locator的。
- org.springframework.cloud.bootstrap.config.PropertySourceLocator=... ○ 这个键是 PropertySourceLocator 类型的类。 ○ PropertySourceLocator 用于在应用程序上下文初始化之前提供额外的 PropertySource 实例。它允许您在应用程序的标准配置加载之前添加自定义配置源。 ○ 但是,只声明 PropertySourceLocator 类型并不会自动将其实例化并添加到 Spring 容器中。它需要通过其他机制(如 BootstrapConfiguration)来触发实例化。
- org.springframework.cloud.bootstrap.BootstrapConfiguration=... ○ 这个键用于定义在 Spring Cloud 应用程序的引导阶段要加载的配置类。 ○ 引导阶段在标准的 Spring Boot 应用程序上下文初始化之前发生。它允许应用程序在常规的配置加载之前设置额外的配置源或执行其他自定义初始化逻辑。 ○ 当您在 spring.factories 文件中使用 BootstrapConfiguration 键并提供类的全限定名时,Spring Cloud 将在引导阶段自动实例化这个类,并执行其配置逻辑。
因此,第二种方法有效是因为它符合 Spring Cloud 引导过程中的预期行为,确保了在正常的 Spring Boot 配置阶段之前执行自定义配置类的逻辑。而第一种方法仅声明了一个 PropertySourceLocator 类型,但没有提供自动实例化和引导集成的机制。 那么到此为止我们就简单的完成了对PropertySourceLocator 的使用,接下来我们先了解一下他的原理。 还记得上面debug的时候,有一个PropertySourceBootstrapConfiguration。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered
它实现了ApplicationContextInitializer,ApplicationContextInitializer 用于在 Spring 容器刷新之前自定义应用程序上下文。它允许开发人员编程方式地对 ConfigurableApplicationContext 进行操作,例如添加属性源或激活特定的配置文件。
他要求我们实现initialize方法。这个方法会在容器被刷新之前自动执行。
而这个PropertySourceBootstrapConfiguration它的initialize方法就帮助我们执行了locator的locate方法。从而帮助我们完成了对配置文件的加载。
上面的for循环就是遍历所有的locator,然后下面的for循环就是add添加这些PropertySource到我们的Environment中去。又由于这些配置是再容器刷新之前执行的,所以我们的@Value注解可以拿到这些配置值。
而且特别注意一个点,PropertySourceBootstrapConfiguration是spring-cloud的依赖提供的,所以如果你的项目不是spring-cloud类型的项目,那么操作起来有点复杂。
这里由于我们既然开发的是配置中心了,自然就是一个spring-cloud项目,所以我会直接通过引入这个依赖的方式来完成locator的加载,而不是自己实现ApplicationContextInitializer的方式来加载locator。
PropertySourceLoader
在SpringBoot配置文件加载的那一篇我们讲到过,SpringBoot提供了一些解析配置文件的类。
他们都实现了PropertySourceLoader接口。
但是SpringBoot只提供了为数不多的实现,因此,如果你需要自研一个配置中心,并且配置中心提供多种不同的配置文件类型,那么你就需要自己实现这个接口。
不过写起来也比较简单,只要按照你自定义的配置文件的格式进行解析即可。
package blossom.project.config.core;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author: ZhangBlossom
* @date: 2023/12/29 21:27
* @contact: QQ:4602197553
* @contact: WX:qczjhczs0114
* @blog: https://blog.csdn.net/Zhangsama1
* @github: https://github.com/ZhangBlossom
* BlossomJsonPropertySourceLoader类
*/
@Deprecated
public class BlossomJsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[]{"json"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
if (!resource.exists()) {
return Collections.emptyList();
}
try {
ObjectMapper mapper = new ObjectMapper();
// 将JSON文件转换为Map
Map<String, Object> source = mapper.readValue(resource.getInputStream(), Map.class);
// 创建一个新的PropertySource
PropertySource<?> propertySource = new MapPropertySource(name, source);
return Collections.singletonList(propertySource);
} catch (IOException e) {
// 处理读取JSON文件时的异常
throw new IOException("Failed to parse JSON resource: " + resource.getDescription(), e);
}
}
}