• 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));
    		}
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其中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);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    获取主启动类的包路径和配置了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));
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

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

    主要是配置了两个过滤器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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    到此整个@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);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    那么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"));
    			}
    		};
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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;
    	}
    
    • 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
    • 30
    • 31
    • 32

    这里面比较复杂,具体的步骤源码不再展开,具体说说大致流程。
    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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果设置了,在最后一个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);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这个方法会从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.的postProcessEnvironment的方法

    void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
    			Collection<String> additionalProfiles) {
    		try {
    			this.logger.trace("Post-processing environment to add config data");
    			resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
    			// 先生成ConfigDataEnvironment对象,核心是根据指定location构造类型为Kind.INITIAL_IMPORT的ConfigDataEnvironmentContributor
    			// processAndApply()将进行解析
    			getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
    		}
    		catch (UseLegacyConfigProcessingException ex) {
    			this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
    					ex.getConfigurationProperty()));
    			configureAdditionalProfiles(environment, additionalProfiles);
    			postProcessUsingLegacyApplicationListener(environment, resourceLoader);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    该方法的核心方法是getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();这个方法可以分成两部分,一部分getConfigDataEnvironment(environment, resourceLoader, additionalProfiles) 使用来创建 ConfigDataEnvironment对象的
    另一部分就是通过ConfigDataEnvironment的processAndApply()进行配置解析。
    其中ConfigDataEnvironment有一些默认路径来找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 配置的参数

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

  • 相关阅读:
    基础 | 并发编程 - [锁]
    多态的讲解
    八大排序-python
    2022.8.7-----leetcode.636
    C语言的文件操作(炒详解)
    js 正则限制只能输入一位小数
    c语言贪吃蛇游戏
    Servlet详解(下)
    TypeScript 笔记:String 字符串
    安卓APP源码和设计报告——好再来点餐
  • 原文地址:https://blog.csdn.net/admin522043032/article/details/126745085