干货满满-springboot底层如何加载配置文件-看完你就知道了

Springboot 配置文件加载源码分析

springboot默认配置文件名为application.yml,application.yaml,application.properties
当我们在配置文件文件中加入配置后重启springboot,配置信息如何被springboot识别?本篇文章将从源码入手按照如下过程进行分析

  • springboot监听器初始化
  • springboot事件发布器初始化
  • springboot监听器工作原理

Springboot监听器机制分析

springboot通过声明主类启动,如下代码所示,根据入口函数按图索骥分析内部原理,入口函数其实很简单只调用了一个静态run方法

@SpringBootApplication
public class FrameworkApplication {
	public static void main(String[] args) {
		SpringApplication.run(FrameworkApplication.class, args);
	}
}	

在run方法中隐藏着启动细节,进入run方法真实逻辑为创建SpringApplication对象并调用该对象的run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

首先分析SpringApplication构造函数,其功能就是初始化包括如下内容

  • 资源加载器初始化
  • 设置启动主类为primarySources
  • 判断当前应用是否是web应用
  • 设置初始化器
  • 设置监听器 (本文重点分析此处逻辑)
  • 设置主类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

此处重点分析setListeners方法,此方法比较简单代码如下

  • 将传入的监听器类赋值给一个List集合
private List<ApplicationListener<?>> listeners;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
	this.listeners = new ArrayList<>(listeners);
}

ApplicationListener是基于JDK观察者模式设计的接口 类图如下
ApplicationEvent,EventListener均为JDK中默认接口

观察者模式
查看完setListeners方法,继续分析该方法入参调用函数getSpringFactoriesInstances,该方法加载type类型的所有class全限定名并进行初始化

  • SpringFactoriesLoader.loadFactoryNames(type, classLoader) 此方法加载所有jar包中META-INF/spring.factories文件
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
	}

通过springboot的源码中发现ApplicationListener类在下图中有10个其中打购类为配置文件解析监听器,这些类通过反射创建完成后传入setListeners完成赋值操作
在这里插入图片描述
到此SpringApplication对象的创建工作结束了,接下来分析run方法中逻辑,由于run方法逻辑比较复杂本文只分析与配置文件加载相关代码

run 方法是springboot启动的核心包括容器初始化,bean加载等重要逻辑

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  configureHeadlessProperty();
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting();
  try {
	 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
	 configureIgnoreBeanInfo(environment);
	 Banner printedBanner = printBanner(environment);
	 context = createApplicationContext();
	 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
	 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	 .....省略代码.....
	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

SpringApplicationRunListeners 该接口为springboot事件发布器,其实现类为EventPublishingRunListener类图如下
以下代码完成两件事情

  • 获取springboot 事件发布器
  • 发布springboot 容器启动事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

EventPublishingRunListener
EventPublishingRunListener 中持有SpringApplication对象,该对象持有所有ApplicationListener对象因此可以实现发布事件到所有监听器

在这里插入图片描述
EventPublishingRunListener starting()方法就是对所有监听器发布容器启动事件,该事件会被独有监听器接受并判断是否处理,处理配置文件的监听器ConfigFileApplicationListener也会收到该事件

public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

ConfigFileApplicationListener处理的事件类型为ApplicationEnvironmentPreparedEvent,ApplicationPreparedEvent,因此ApplicationStartingEvent并不会处理
在这里插入图片描述
往下继续查看run方法中代码会发现prepareEnvironment方法会创建ConfigurableEnvironment类此类是springboot中存储所有配置的类

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

进入该方法发现事件发布器向所有坚挺着发布一个环境准备完毕的事件此事件正式上文提到的ApplicationEnvironmentPreparedEvent因此会触发ConfigFileApplicationListener处理,但是此时必须已经创建ConfigurableEnvironment类
在这里插入图片描述
ConfigFileApplicationListener监听到ApplicationEnvironmentPreparedEvent事件处理以下几件事情

  • 加载后置处理器
  • 对后置处理器优先级进行排序
  • 调用后置处理器处理逻辑

在这里插入图片描述
后置处理器是所有EnvironmentPostProcessor实现类加载方式与监听器一样可参考上文,ConfigFileApplicationListener同样实现了EnvironmentPostProcessor后置处理器接口因此postProcessors.add(this); 是将自己注册到所有处理器中实现处理逻辑

loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,getClass().getClassLoader());
}

这里特此声明真正实现配置文件加载的逻辑开始
postProcessEnvironment调用addPropertySources调用Loader(environment, resourceLoader).load(),所有的逻辑处理都是通过Loader 这个内部类来完成的,此处的处理方式跟SpringApplication(primarySources).run(args) 是不是极为相似
在这里插入图片描述
最后分析下Loader类的逻辑首先初始化完成

  • springboot环境类用于存放所有配置文件中的key=value
  • 变量占位符解析类
  • 资源加载器
  • 配置文件解析加载器,处理 properties,yml,yaml文件的加载

在这里插入图片描述
PropertySourceLoader实现类为下图中两个,因此配置文件的加载会根据文件扩展名不同使用不用类来处理
在这里插入图片描述
重要的逻辑终于出来了,此处重点逻辑分为

  • 处理spring.profiles.active,spring.profiles.include配置
  • 加载配置文件并与profile绑定
  • 将配置文件与springboot Environment 绑定
  • 激活对应的profile

在这里插入图片描述
load方法首先会在几个默认路径下尝试加载,默认路径如下

DEFAULT_SEARCH_LOCATIONS = “classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/”;

getSearchNames()方法返回配置文件名字默认名字为:application
在这里插入图片描述
此处特别说明下如果启动方式为 java -jar xxx.jar -Dspring.config.name=xx.properties 则不会加载默认名字的配置文件,网上很多关于spring.config.name使用这里通过源码分析说明了使用方式
在这里插入图片描述
接下来是load方法因为name非空则直接进入红框逻辑,通过扩展名来处理不同类型配置文件

在这里插入图片描述
如果没有配置spring.profile.active直接进入红框逻辑
在这里插入图片描述
通过resourceLoader加载路径下配置文件通过合法行校验后加载对应配置文件等待下一步解析
在这里插入图片描述
配置文件加载完毕后进行解析此处consumer lambda方法为addToLoaded并将其与
Map<Profile, MutablePropertySources> loaded 绑定
在这里插入图片描述
最后是将配置与enviroment 绑定
在这里插入图片描述

写在最后

这是一篇源码流水账文章,记录了怎么跟springboot源代码进行配置文件加载原理分析,如果你跟着我的步骤一步一步debug肯定是有所收获的,由于篇幅原因Enviroment实现类分析没有展开

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值