首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一. 前言
文章目的 :
- 梳理 Applicaiton 的加载方式
- 梳理 Profile 的处理
二 . 扫描的触发
启动的源头任然是SpringApplication#run , 回顾之前的一篇源码 , 在 SpringApplication 中 ,会执行一段代码 :
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
而一切的起点就是那里 , SpringBoot 通过这一句加载所有的环境信息 :
2.1 SpringApplication # prepareEnvironment
在该环节中 , 对 Environment 进行操作的处理 , 其中包括几个主要的操作 :
- ConfigurableEnvironment 的生成
- configureEnvironment 细粒度处理
- 对 configurationProperties 属性进行处理
- 发布 listener 处理执行不同类型配置文件的处理
- 将 environment 绑定到 SpringApplication
C- SpringApplication # prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 内部通过 WebApplicationType 生成不同的 Environment (可以set 自己的 Environment)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 重写此方法以完全控制环境自定义,或者重写上述方法之一以分别对属性源或概要文件进行细粒度控制。
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 对 configurationProperties 属性进行处理
ConfigurationPropertySources.attach(environment);
// 发布 listener 处理
listeners.environmentPrepared(environment);
// 将 environment 绑定到 SpringApplication
bindToSpringApplication(environment); -> M1_25
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
2.1.1 ConfigurableEnvironment 的生成
此处会生成一个 StandardServletEnvironment , 他是 ConfigurableEnvironment 的实现类 , 该对象中存在2个方法 , 分别配置了多个 Source 对象
- customizePropertySources(MutablePropertySources propertySources)
- initPropertySources(ServletContext servletContext,ServletConfig servletConfig)
// Servlet上下文初始化参数属性源名称
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
// Servlet config init parameters属性源名称
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
// 加载系统属性
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
protected void customizePropertySources(MutablePropertySources propertySources) {
// 这2步主要添加2个空对象
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
// 伪代码 , 来自于父类 , 此处用于加载系统属性 -> PRO:0001
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
// PS : 这里的多个Sources 将会用于优先级处理
SERVLET_CONFIG_PROPERTY_SOURCE_NAME > SERVLET_CONTEXT_PROPERTY_SOURCE_NAME > JNDI_PROPERTY_SOURCE_NAME
// [PRO:0001] 系统属性的获取方式 ?
return (Map) System.getProperties();
[PRO:0001] 系统属性大概样式 >>>
其中最主要的就是第 4 步 , 该步骤中扫描不同的配置处理类
补充 : getOrCreateEnvironment 的获取
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
2.2 发布 listener 处理执行不同类型配置文件的处理
前面主要是获取 SystemSource , 还没有正式开始 , 我们从 prepareEnvironment 第四步开始看 , 来看看后面怎么处理 :
Step 1 : 发布 Listener , 推动 Enviroment 处理
这里不需要深入太多 , 标准的 Listener 发布方式 , 这里主要发布的是 ApplicationEnvironmentPreparedEvent
C- SpringApplicationRunListeners
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
// PS : 此处使用的 Listener 为 ApplicationEnvironmentPreparedEvent
listener.environmentPrepared(environment);
}
}
Step 2 :ConfigFileApplicationListener 处理该事件
核心的处理方式就是 EnvironmentPostProcessor 的循环 , 后面所有的操作均在其中完成 :
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
// 可以看到 , 此处还是通过 Processors 进行处理 -> PS:0001
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
三 . postProcessors 的处理
前面看了 , 通过调用 postProcessor.postProcessEnvironment 来实现不同的环境加载 , 可以看到 , 此处主要有5个 postProcessors :
PS:0001 ConfigFileApplicationListener 中 postProcessors 有哪些
- SystemEnvironmentPropertySourceEnvironmentPostProcessor
- SpringApplicationJsonEnvironmentPostProcessor
- CloudFoundryVcapEnvironmentPostProcessor
- ConfigFileApplicationListener(没错 , 他本身也是个 EnvironmentPostProcessor)
- DebugAgentEnvironmentPostProcessor
3.1 SystemEnvironmentPropertySourceEnvironmentPostProcessor
该类主要是对 systemEnvironment 进行处理 , 前面看到了 , 实际上前面已经拿到 SystemSource , 此处是对这些配置的二次处理 :
public void postProcessEnvironment(
ConfigurableEnvironment environment, SpringApplication application) {
// >>> systemEnvironment
String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
// 从 environment 中获取 systemEnvironment 的所有属性
PropertySource<?> propertySource = environment.getPropertySources().get(sourceName);
if (propertySource != null) {
replacePropertySource(environment, sourceName, propertySource);
}
}
// 此处主要是把 SystemEnvironmentPropertySource 封装成了 OriginAwareSystemEnvironmentPropertySource
private void replacePropertySource(ConfigurableEnvironment environment, String sourceName,
PropertySource<?> propertySource) {
Map<String, Object> originalSource = (Map<String, Object>) propertySource.getSource();
SystemEnvironmentPropertySource source
= new OriginAwareSystemEnvironmentPropertySource(sourceName,originalSource);
environment.getPropertySources().replace(sourceName, source);
}
[Pro] OriginAwareSystemEnvironmentPropertySource 是什么 ?
// OriginAwareSystemEnvironmentPropertySource 是SystemEnvironmentPropertySource 的子类,
// 提供了获取Origin的方法,即返回SystemEnvironmentOrigin对象
SystemEnvironmentOrigin 提供对原始属性名的访问
3.2 SpringApplicationJsonEnvironmentPostProcessor
该类的主要使用形式是在启动 jar 包时使用 , 这种方式的优先级最高:
java -jar xxx.jar --spring.application.json={\"username\":\"ant-black\"}
// 从 spring.application.json或等价的SPRING_APPLICATION_JSON中解析JSON ,
// 并将其作为映射属性源添加到环境中
public void postProcessEnvironment(
ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
.ifPresent((v) -> processJson(environment, v));
}
private void processJson(ConfigurableEnvironment environment, JsonPropertyValue propertyValue) {
JsonParser parser = JsonParserFactory.getJsonParser();
// 使用 JsonParser 将 JSON 解析为 Map 集合
Map<String, Object> map = parser.parseMap(propertyValue.getJson());
if (!map.isEmpty()) {
addJsonPropertySource(environment, new JsonPropertySource(propertyValue, flatten(map)));
}
}
// 这里可以看到 , spring.application.json 添加的优先级是最高的
private void addJsonPropertySource(ConfigurableEnvironment environment, PropertySource<?> source) {
MutablePropertySources sources = environment.getPropertySources();
String name = findPropertySource(sources);
if (sources.contains(name)) {
// 添加优先级立即高于命名相对属性源的给定属性源对象
sources.addBefore(name, source);
} else {
sources.addFirst(source);
}
}
3.3 CloudFoundryVcapEnvironmentPostProcessor
一个环境 postprocessor,它知道在现有环境中在哪里找到VCAP(也就是云计算)元数据。它解析VCAP_APPLICATION和VCAP_SERVICES元数据,并将其转储Environment。
这一块比较玄妙 , 首先要知道 VCAP 是什么 :
- IBM Cloud 中有一个概念 : Cloud Foundry 应用程序
- 在 Cloud Foundry 运行的应用程序通过存储在一个名为 VCAP services 的环境变量中的凭证获得对绑定服务实例的访问
- VCAP_APPLICATION 是指云计算的应用单体元数据
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
Properties properties = new Properties();
JsonParser jsonParser = JsonParserFactory.getJsonParser();
addWithPrefix(properties, getPropertiesFromApplication(environment, jsonParser), "vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment, jsonParser), "vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
}
简单来说 , 这个类主要是为了云计算的环境准备的 , 因为没有太多涉及 , 此处就不深入了
3.4 ConfigFileApplicationListener
这一块应该就是最核心的一块了 , 这里会对 Application 文件进行扫描处理 :
Step 1 : ConfigFileApplicationListener 的入口
由于 ConfigFileApplicationListener 本身继承了 EnvironmentPostProcessor , 其本身也会加载配置环境 :
// 将配置文件属性源添加到指定的环境
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
Step 2 : 构建 Loader 对象
// 来看一看 loader 对象 , 该对象会用于处理属性 Document 并且扫描
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
//
this.environment = environment;
// 解析器 , 用于解析占位符
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 资源加载器
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 属性加载器 , 这里是从 SpringFactories 中加载
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
// 我们省略其中的一些流程 , 直接关注主流程 :
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
// 处理 Profile
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
Step 3 : 扫描所有的路径 , 发起处理
此处通过 getSearchLocations 获得所有的根路径 , 然后依次对根路径下的文件进行扫描
PS :因为处理的顺序不同 , 更靠后的优先级更高
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 获取所有的路径 , 并且对路径进行扫描
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// 注意 , 这里 SearchName 是获取所有扫描的文件名 , 可以通过 spring.config.name 配置
// 默认名称 application
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
这里主要有四个默认路径 :
- file:./config/
- file:./
- classpath:/config/
- classpath:/
Step 4 : 对路径进行依次处理
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException(".....");
}
Set<String> processed = new HashSet<>();
// PropertySourceLoader -> PS:0002
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 此处获取额外的属性 , 此处为 yml 和 yaml
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
// 此处拿到的值为 classpath:/application
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,consumer);
}
}
}
}
PS:0002 PropertySourceLoader 的类型
此处提供了 2 种 PropertySourceLoader ,分别是 PropertiesPropertySourceLoader (.properties) 和 YamlPropertySourceLoader (支持 .yml .yaml')
Step 5 : loadForFileExtension 处理
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// 尝试配置文件中特定配置文件和配置文件段(gh-340)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// 尝试分析我们已经处理过的文件中的特定部分
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
Step 6 : 对 Application 文件进行读取
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
// PS : 当依次对四个路径进行处理的时候 , 如果不存在会由此逻辑返回
return;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
return;
}
String name = "applicationConfig: [" + location + "]";
// 通过 Document 加载文档 , 此处为 applicationConfig: [classpath:/application.yml]
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
// PS : 如果文档为空 ,会从此逻辑返回
return;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) {
// 处理 Profiles 文件
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
}
}catch (Exception ex) {
throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
}
}
Step 7 : 调用 loader 流程发起加载
该逻辑就是对Document 的处理了 , 也不用太深入
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
// 此处 loader 为 org.springframework.boot.env.YamlPropertySourceLoader
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
// PS : 此处暂时就可以不用关注了 , 后续主要是Loader 的加载逻辑 , 可以参考之前的文档 :
I- PropertySourceLoader
C- YamlPropertySourceLoader
C- PropertiesPropertySourceLoader
3.5 DebugAgentEnvironmentPostProcessor
作用 : 启用反应堆调试代理
开启 : 调试代理默认是启用的,除非 spring.reactor.debug-agent.enabled 配置属性设置为false
// 这是个特殊的类 , 目的是为了快速加载
private static final String DEBUGAGENT_ENABLED_CONFIG_KEY = "spring.reactor.debug-agent.enabled";
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (ClassUtils.isPresent(REACTOR_DEBUGAGENT_CLASS, null)) {
Boolean agentEnabled = environment.getProperty(DEBUGAGENT_ENABLED_CONFIG_KEY, Boolean.class);
if (agentEnabled != Boolean.FALSE) {
try {
Class<?> debugAgent = Class.forName(REACTOR_DEBUGAGENT_CLASS);
// 调用init 方法初始化
debugAgent.getMethod("init").invoke(null);
} catch (Exception ex) {
throw new RuntimeException("Failed to init Reactor's debug agent");
}
}
}
}
这个类的主要目的就是为了初始化 REACTOR_DEBUGAGENT_CLASS 类 , 而不是为了加载配置 , 这种方式更加快 (学到了!!!!)
四 . 知识点
4.1 Profile 的处理
Step 1 : 当第一步扫描 Document 的时候 , 会从其中获得 profile 属性 , 并且标注从来
Step 2 : 处理 Profiles
起因是上文 Step 6 中调用的 addActiveProfiles 和 addIncludedProfiles
void addActiveProfiles(Set<Profile> profiles) {
if (profiles.isEmpty()) {
return;
}
if (this.activatedProfiles) {
return;
}
this.profiles.addAll(profiles);
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
}
// 此处设置了 ConfigFileApplicationListener 的 Profiles 属性
private void addIncludedProfiles(Set<Profile> includeProfiles) {
LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
this.profiles.clear();
this.profiles.addAll(includeProfiles);
this.profiles.removeAll(this.processedProfiles);
this.profiles.addAll(existingProfiles);
}
// PS:此处设置完成后 ,Profiles 就会由 default 变成 test
到这里还没完 , 还要做相关的处理 :
Step 3: 反复处理 Profiles
回顾上文 Step 5 : loadForFileExtension 处理 中 , 会发现这样一个处理
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
// ........ 省略
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
// 核心 , profile 再次处理
load(loader, profileSpecificFile, profile, profileFilter, consumer);
//.........
}
没错 , 这里就是把 profile传进去原样再扫描一遍 , 而值就是上一步设置的 value
你以为这就完了吗 ? 这就像盗梦空间里面一样 , 你以为你醒了 ,其实还在一个梦里 , 整个逻辑中涉及多个循环处理 , 而 Profile 的循环开启是在第一层
// 当第一遍大循环执行玩抽 , profiles 中就已经有了新的 profiles , 此处会开启第二个大循环
// 此处使用 Deque<Profile> profiles , 以弹出的方式获取对象
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
补充一下循环体系 :
- Loader # load : while 循环 profile
- ConfigFileApplicationListener # load(1) : foreach location
- ConfigFileApplicationListener # load(1) : foreach names (PS : 这个循环在上个循环的内部)
- ConfigFileApplicationListener # load(2) : for 循环 propertySourceLoaders
- ConfigFileApplicationListener # load(2) : for 循环 FileExtensions
load(1) : load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)
load(2) : load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer)
4.2 具体的加载顺序
Step 1 : 后缀文件的加载顺序
从 ConfigFileApplicationListener # load 方法种 , 我们可以看到 , 加载顺序依次是 :
- properties
- xml (没错 , 他会试图加载 xml 文件)
- yml
- yaml
总结
本来以为篇幅不会太长, 结构又写了这么多 , 所以将属性的转换放在下一篇将 ,欢迎点赞收藏
修改记录
- V20210612 : 添加 >> 四 . 知识点
- V20210804 : 添加结构图