IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springBoot 源码五:springboot启动源码补充和配置优先级 -> 正文阅读

[Java知识库]springBoot 源码五:springboot启动源码补充和配置优先级

springboot启动源码补充和配置优先级

上一篇分析了springboot的启动流程,其中还有一些部分没有分析完全。接下来通过一下方面进行分析

  • @SpringBootApplication注解
  • 启动流程中的第五步,准备环境变量部分 这部分和springboot配置优先级有关
  • callRunners(context, applicationArguments); 方法解释
  • 对启动流程的总结

@SpringBootApplication注解分析

先来看下@SpringBootApplication注解有哪些内容
在这里插入图片描述
在这里插入图片描述
@SpringBootApplication包含了以上几个注解和属性。

先来分析几个常用的属性
exclude()和excludeName()都是用来排除自动配置类的。注意这里排除的是自动配置类,不是我们定义的bean。这部分源码在springboot自动配置源码分析中有分析过
scanBasePackages()和scanBasePackageClasses()配置对应的扫描路径,这部分的内容是spring的配置扫描的内容Bean的生命周期源码解析中分析过扫描流程
nameGenerator() 指定使用BeanNameGenerator类型,BeanNameGenerator类型用来创建bean的名字的,一般都不会配置
proxyBeanMethods() 这个是spring的内容,在spring配置类解析源码解析中提到过

在来看看SpringBootApplication的注解
@SpringBootConfiguration
实际上就是 @Configuration spring的内容 标注它的类被spring当成配置类来解析
在这里插入图片描述
@EnableAutoConfiguration 开启自动配置的注解
在这里插入图片描述
其中@Import(AutoConfigurationImportSelector.class)在自动配置底层源码分析中分析了
@AutoConfigurationPackage 的两个属性的值 就是@SpringBootApplication对应的属性配置的值
在这里插入图片描述
在这里插入图片描述
然后来看下@Import(AutoConfigurationPackages.Registrar.class)做了什么?

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

其中new PackageImports(metadata).getPackageNames().toArray(new String[0])

PackageImports(AnnotationMetadata metadata) {
			//把AutoConfigurationPackage的属性值封装成AnnotationAttributes 对象
			AnnotationAttributes attributes = AnnotationAttributes
					.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
			//从AnnotationAttributes 获取属性名为basePackages的值 也就是获取配置的扫描路径
			List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
			//遍历配置的扫描类 把对应包路径添加到packageNames中
			for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
				packageNames.add(basePackageClass.getPackage().getName());
			}
			// 添加主启动类的包路径 也就是MyApplicatoin类所在的包
			if (packageNames.isEmpty()) {
				packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
			this.packageNames = Collections.unmodifiableList(packageNames);
		}

获取主启动类的包路径和配置了basePackages和basePackageClasses的包路径。
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			//BeanDefinition注册器中如果有AutoConfigurationPackages这个BeanDefinition
			//从BeanDefinition注册器中获取AutoConfigurationPackages的BeanDefinition
			BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
			//把扫描路径添加到AutoConfigurationPackages的BeanDefinition中
			beanDefinition.addBasePackages(packageNames);
		}
		else {
			//创建一个BeanDefinition 名字为autoConfigurationPackages
			registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
		}
	}

把获取到的扫描路径生成BeanDefinition保存起来,一方面给后续执行扫描逻辑时使用,一方面给第三方框架获取扫描路径使用
总结起来就是@EnableAutoConfiguration注解,一方面开启自动配置功能,一方面获取主启动类所在的包作为扫描路径

@ComponentScan
spring的扫描注解,在springboot中

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

主要是配置了两个过滤器AutoConfigurationExcludeFilter和TypeExcludeFilter
以AutoConfigurationExcludeFilter为例
在这里插入图片描述
这个过滤器会通过match方法判断当前加载bean是否有@Configuration注解,并且是否是META-INF/spring.factories下的自动配置类当中的。如果是返回true,表示springboot不处理这个类,相当于过滤掉,如果不是返回false,springboot回去解析这个类。
总结下来就是:AutoConfigurationExcludeFilter的作用是扫描到的配置类名字如果在自动配置类名集合中,就不解析
TypeExcludeFilter
同理

@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
		if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {

			// 从Spring容器中获取TypeExcludeFilter,然后进行匹配,匹配的则不解析
			for (TypeExcludeFilter delegate : getDelegates()) {
				if (delegate.match(metadataReader, metadataReaderFactory)) {
					return true;
				}
			}
		}
		return false;
	}

到此整个@SpringBootApplication就分析完毕

callRunners(context, applicationArguments); 方法解释

这是在springboot启动路程中的第十步,过程大概是,获取Spring容器中的ApplicationRunner类型和CommandLineRunner类型的Bean,执行它们的run()。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);

		for (Object runner : new LinkedHashSet<>(runners)) {

			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}

			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

那么ApplicationRunner和CommandLineRunner这两个接口是用来干嘛的呢,实际上就是对命令行参数的处理。举个例子

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

	}

	@Bean
	public ApplicationRunner applicationRunner(){
		return new ApplicationRunner() {
			@Override
			public void run(ApplicationArguments args) throws Exception {
				//输出带有--的参数 key 和value
				System.out.println(args.getNonOptionArgs());
				//输出不带--的key
				System.out.println(args.getOptionNames());
				//输出不带--的key对应的value
				System.out.println(args.getOptionValues("aa"));
			}
		};
	}

}

springboot启动类中定义了一个bean ,之前说过args的值就是命令行参数。所以在idea中模拟命令行参数
在这里插入图片描述
启动springboot
在这里插入图片描述

启动流程中的第五步,准备环境变量部分和SpringBoot配置优先级

先来看下这部分源码

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// 创建ApplicationServletEnvironment,里面添加了四个PropertySource
		// 1. StubPropertySource {name='servletConfigInitParams'}
		// 2. StubPropertySource {name='servletContextInitParams'}
		// 3. PropertiesPropertySource {name='systemProperties'}
		// 4. SystemEnvironmentPropertySource {name='systemEnvironment'}
		ConfigurableEnvironment environment = getOrCreateEnvironment();

		// 添加SimpleCommandLinePropertySource {name='commandLineArgs'},放在首位
		configureEnvironment(environment, applicationArguments.getSourceArgs());

		// 把所有的PropertySources封装为一个ConfigurationPropertySourcesPropertySource
		// 然后添加到environment中,放在首位
		ConfigurationPropertySources.attach(environment);

		// 发布ApplicationEnvironmentPreparedEvent事件,表示环境已经准备好了
		// 默认EnvironmentPostProcessorApplicationListener会处理这个事件,会从spring.factories中拿出EnvironmentPostProcessor进一步处理Environment
		listeners.environmentPrepared(bootstrapContext, environment);

		// 最后,把defaultProperties移到最后
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

这里面比较复杂,具体的步骤源码不再展开,具体说说大致流程。
ConfigurableEnvironment environment = getOrCreateEnvironment();
首先创建一个ConfigurableEnvironment 环境变量的对象。这个过程中往ConfigurableEnvironment 条件四个PropertySource。每个PropertySource相当于properties文件解析后或者理解成一个map。一开始这四个PropertySource分别是

  1. StubPropertySource {name=‘servletConfigInitParams’} servlet的初始化参数的键值对
  2. StubPropertySource {name=‘servletContextInitParams’} web容器的初始化参数
  3. PropertiesPropertySource {name=‘systemProperties’} jvm参数和-D配置的参数
  4. SystemEnvironmentPropertySource {name=‘systemEnvironment’} 操作系统的参数

当我们需要用到配置参数的时候就会从这些PropertySource里面拿,那么是怎么拿的呢?,springboot会从第一个开始,拿到了就直接使用,这就是优先级了。

configureEnvironment(environment, applicationArguments.getSourceArgs());
这个方法中,先判断有没有设置DefaultProperties,例如

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
//		SpringApplication.run(Application.class, args);
		SpringApplication springApplication = new SpringApplication(Application.class);
		Map<String,Object> map = new HashMap<>();
		map.put("server.port",8009);
		springApplication.setDefaultProperties(map);
		springApplication.run(args);
	}

如果设置了,在最后一个PropertySource后面添加一个名字叫defaultProperties的PropertySource。此时就有五个PropertySource

servletConfigInitParams
servletContextInitParams
systemProperties
systemEnvironment
defaultProperties

然后把命令行参数放到名字叫commandLineArgs的PropertySource中,放到最前面。命令行参数也就是java -jar xxx.jar --server.port=8002此时就有6个PropertySource

commandLineArgs :命令行参数
servletConfigInitParams :servlet的初始化参数的键值对
servletContextInitParams :web容器的初始化参数
systemProperties:jvm参数和-D配置的参数
systemEnvironment :操作系统的参数
defaultProperties: SpringApplication 配置的参数

listeners.environmentPrepared(bootstrapContext, environment);
发布一个环境变量准备好的事件,有一个叫EnvironmentPostProcessorApplicationListener的监听器监听到这个事件后会进行后续处理。具体的处理代码

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		SpringApplication application = event.getSpringApplication();

		// 从spring.factories中拿出EnvironmentPostProcessor进一步处理Environment
		// RandomValuePropertySourceEnvironmentPostProcessor
		// SystemEnvironmentPropertySourceEnvironmentPostProcessor
		// SpringApplicationJsonEnvironmentPostProcessor
		// CloudFoundryVcapEnvironmentPostProcessor
		// ConfigDataEnvironmentPostProcessor
		// IntegrationPropertiesEnvironmentPostProcessor
		// DebugAgentEnvironmentPostProcessor
		for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
				event.getBootstrapContext())) {
			postProcessor.postProcessEnvironment(environment, application);
		}
	}

这个方法会从spring.factories拿到一系列的EnvironmentPostProcessor。对environment进行处理,例如第一个RandomValuePropertySourceEnvironmentPostProcessor,就会在systemEnvironment 这个PropertySource后面在增加加一个叫random的PropertySource。此时就有7个PropertySource

commandLineArgs :命令行参数
servletConfigInitParams :servlet的初始化参数的键值对
servletContextInitParams :web容器的初始化参数
systemProperties:jvm参数和-D配置的参数
systemEnvironment :操作系统的参数
random :存储一些随机数 通过random.int 可以获取到int的随机数 同理还有random.long 等等
defaultProperties: SpringApplication 配置的参数

其他的EnvironmentPostProcessor就不在分析,核心来分析下ConfigDataEnvironmentPostProcessor
ConfigDataEnvironmentPostProcessor
首先ConfigDataEnvironmentPostProcessor有一些默认路径来找application.properties 等这些配置文件
在这里插入图片描述
optional:前缀表示允许在该路径下找不到配置文件
也可以通过配置以下参数指定找配置文件的路径

spring.config.location 指定了默认路径就不生效
spring.config.import 在默认路径下新增
spring.config.additional-location 在默认路径下新增

当然这些参数配置在配置文件中是不生效,因为现在就是要去找寻配置文件。
那么这些路径的顺序是怎样的呢,先从那个路劲下开始找呢?
如果通过spring.config.import 或者 spring.config.additional-location配置了新的路径 那么该路径排最前面

新配置的路径
file./
file./config
file./config/*/
classpath:/
classpath:/config/

从上到下的顺序去找配置文件。那么找那些配置文件呢,可以通过spring.config.name进行配置,默认值为application。
所以会从上面几个路径依次去找application.yaml,application.yml,application.properties
按照上述文件顺序找到后,有一个倒序的操作
在这里插入图片描述
所以配置文件的优先级变成properties>yml。如果多个路径下有配置文件,那么先按照路径的优先级,再到文件的优先级。
此时找到的配置文件并没有解析所有配置文件放入到环境变量中,而是解析成PropertySource按顺序放到一个集合当中,判断有没有配置spring.profiles.active。比如说配置了spring.profiles.active=dve,那么就会去找application-dve的配置文件。所以spring.profiles.active=dve的配置需要放在application配置文件下。
找到以后再解析application-dve的配置文件,解析完成后放入到环境变量中,此时application-dve的优先级是高于application的。
此时环境变量的PropertySource 顺序如下

commandLineArgs :命令行参数
servletConfigInitParams :servlet的初始化参数的键值对
servletContextInitParams :web容器的初始化参数
systemProperties:jvm参数和-D配置的参数
systemEnvironment :操作系统的参数
random :存储一些随机数 通过random.int 可以获取到int的随机数 同理还有random.long 等等
application-dve.properties
application-dve.yml
application.properties
application.yml
defaultProperties: SpringApplication 配置的参数

优先级自然从上到下。
到此整个环境配置就完成了

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:59:34  更:2022-09-13 11:02:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:46:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码