springboot3--->springboot的ConfigFileApplicationListener 配置文件应用监听器原理分析

1、简单使用

      我们知道spring boot 默认会加载appication.properties配置文件,我们在此配置文件中配置一个name熟悉,然后写一个rest接口进行获取,代码如下:

         application.properties配置文件中:

               server.port=8080

               name=wen default

          Controller:

    @RestController
    public class TestController {

       @Value("${name}")
       private String name;

       @RequestMapping("name")
       public String name(){
           return name;
       }

    }

           测试结果成功拿到配置:

           

2、各种配置方式总结:


Program arguments  :                配置形式:--name=wen-ProgramArg   在环境类中存放的配置源名称:commandLineArgs                                                        优先级:第1
VM options         :                配置形式:-Dname=wen-vm           在环境类中存放的配置源名称:systemProperties                                                       优先级:第4
application-{profile}.properties    配置形式:name=wen-test           在环境类中存放的配置源名称:applicationConfig: [classpath:/application-{profile}.properties]       优先级:倒数第2 如果一次激活多个,激活的顺序表示优先级,
                                                                                                                                                                         比如:--spring.profiles.active=dev,test 将会添加两个配资源,
																																										       test优先级高于dev																																		 
application.properties              配置形式:name=wen-def            在环境类中存放的配置源名称:applicationConfig: [classpath:/application.properties]                 优先级:倒数第1


注意点1:application.properties可以不需要,但是一定需要指定profile的文件:
         案例:step1:删除application.properties 文件。
		       step2:必须使用系统属性-D 或者使用应用参数-- 来指定profile,如 --spring.profiles.active=dev
			   step3:此时我们就需要添加application-dev.properties 配置文件完成配置。
			   
注意点2:使用系统变量-D  或 应用参数-- 指定了profile的时候,然后我们在配置文件中又指定激活的profile的时候将会无效,
         因为spring boot 不支持已经激活了profile 后又再次激活profile。源码中的原话是"Profiles already activated, '" + profiles + "' will not be applied"。
		 
		 这种情况还有一种情况,比如我们没有使用命令行的形式指定profile,这个时候默认会加载application.properties,如果我们在application.properties中
		 配置了profile=dev,然后我们在application-dev.properties中又配置了profile=test,这个时候不会激活tes环境,理由跟上面一样,不许再次激活profile,
		 不管你激活的是否是同一个profile,都是不行的。
		 
spring.profiles.incloude:表示需要引入的子项配置:
                比如有的环境我们需要使用mybatis配置,有的环境不需要使用mybatis配置,那就可以使用spring.profiles.incloude来进行配置的子项映入,
				案例:--spring.profiles.incloude=mybatis 那么application-mybatis.properties  也将会以profile被激活。

看不全图片显示:

 

3、ConfigFileApplicationListener配置文件监听器源码在整应用上下文生命周期的执行时机分析:

      ConfigFileApplicationListener这个监听器是专门负责根据配置去加载配置文件,当我们不做任何配置的时候,ConfigFileApplicationListener会读取"classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/"等路径下,默认名称是application,后缀名是properties或yml的配置文件,然后将其解析成为一个配置源PropertySource然后添加到环境实例environment中,且添加的顺序是放在最后一位表示优先级最低。

     3.1、ConfigFileApplicationListener的执行时机:

             ConfigFileApplicationListener是一个监听器,了解spring的事件机制才能看懂它的实现方式,既然是事件监听器那么肯定会有自己监听是事件,我们来看一下源码,看看ConfigFileApplicationListener都监听了那些事件:

	    @Override
	    public void onApplicationEvent(ApplicationEvent event) {
		   if (event instanceof ApplicationEnvironmentPreparedEvent) {
			   onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		   }
		   if (event instanceof ApplicationPreparedEvent) {
			  onApplicationPreparedEvent(event);
		   }
	    }

                   由此可以看出来ConfigFileApplicationListener监听了所有的事件,但是只处理两种事件就是:

                       1、ApplicationEnvironmentPreparedEvent :应用环境准备完成事件。

                       2、ApplicationPreparedEvent:应用准备完成事件。

       3.2、ApplicationEnvironmentPreparedEvent是在什么时候发布的呢?

                  ApplicationEnvironmentPreparedEvent事件是在spring 应用的环境实例environment准备完车后发布的,源码实现如下:


    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);

        使用SpringApplicationRunListeners监听器来进行环境准备完成处理,
SpringApplicationRunListener监听器区别于ApplicationListener,ApplicationListener负责处理事件
event,而SpringApplicationRunListener监听器不监听事件,而是在spring应用上下文的生命周期中进行一
些通知,并不是基于事件来通知。
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

	void environmentPrepared(ConfigurableEnvironment environment) {

        找到所有的SpringApplicationRunListener实例来进行调用,这里的listeners是通过SPI获取的,
默认只有一个EventPublishingRunListener实例,这个EventPublishingRunListener负责事件发布与执行监
听器。
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

           加下来我们来到EventPublishingRunListener的environmentPrepared(ConfigurableEnvironment environment):

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {

        使用一个事件广播器广播一个ApplicationEnvironmentPreparedEvent应用环境准备好的事件出去,
这样我们的ConfigFileApplicationListener就能监听到这个事件,ConfigFileApplicationListener是同步
执行的,应为我们的事件广播器并没有配置一个线程池,所以是同步执行的。

		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

         而我们的ConfigFileApplicationListener是什么时候实例化的呢?在项目启动阶段就会通过SPI的方式直接获取到ConfigFileApplicationListener的实例然后添加到应用 山下文中,然后就来到了ConfigFileApplicationListener的onApplicationEvent(ApplicationEvent event)方法了。

 

4、ConfigFileApplicationListener处理ApplicationEnvironmentPreparedEvent应用环境准备完成事件的原理:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {

		if (event instanceof ApplicationEnvironmentPreparedEvent) {
            处理应用上下文的环境准备完成事件
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

           

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        1、先获取环境后置处理器列表,获取方式也是使用SPI。
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();

        2、将当前的ConfigFileApplicationListener也加入到环境后置处理器中,因为
ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口,所以也是一个环境后置处理器。
		postProcessors.add(this);

        3、将环境后置处理器进行排序。
		AnnotationAwareOrderComparator.sort(postProcessors);

        4、调用所有的环境后置处理器,我们就讲解ConfigFileApplicationListener这个环境后置处理器
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

           ConfigFileApplicationListener即是一个监听器也是一个环境后置处理器,其处理器上下文环境的实现如下:

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        添加配资源,可能会添加多个,到这一步的时候,环境类中肯定已经存在如下配置源了: 
                commandLineArgument
                如果是环境下还会有servlet的两个配置源
                systemProperties
                systemEnvironment
		addPropertySources(environment, application.getResourceLoader());
	}

         添加配资源的实现:

	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        1、先添加一个随机值的配资源,这个就是专门解析一些spring表达式的,如配置server.port=${random.int}
		RandomValuePropertySource.addToEnvironment(environment);

        2、构建一个内部类Loader实例,然后加载。
		new Loader(environment, resourceLoader).load();
	}


Loader的构造函数:
		Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {

            1、将Loader实例的environment属性设置为之前准备好的环境实例
			this.environment = environment;
  
            2、将当前的占位符解析器设置为new的一个PropertySourcesPlaceholdersResolver实例。
			this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);

            3、设置当前Loader实例的资源载入器。
			this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);

            4、使用SPI的方式将当前Loader实例的配置源载入器列表进行设置。
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
					getClass().getClassLoader());
		}

         Loader实例的load()方法实现:

		void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {

                        1、创建一个空的集合赋值到当前Loader实例的双端队列属性profiles,表示当
                           前激活的profile列表。
						this.profiles = new LinkedList<>();

                        2、创建一个空集合赋值到当前Loader实例的已经处理好的profile列表。
						this.processedProfiles = new LinkedList<>();

                        3、当前Loader实例的activatedProfiles这个参数很重要,它决定了当前应用
                          上下文已经激活了profile还能否再次被激活,这个会决定我们的使用。
						this.activatedProfiles = false;

                        4、loaded 属性表示是被加载的属性源,结构是Map,key=profile 
                           value=MutablePropertySources
						this.loaded = new LinkedHashMap<>();

                        5、初始化profile,这里会获取到在这里之前激活的profile列表,然后赋值到 
                           当前Loader实例的profiles中。
						initializeProfiles();
                       
                        6、如果激活的profile列表不为空,那就进行循环配资源载入处理,这里就是
                           spring boot支持多环境配置的实现。
						while (!this.profiles.isEmpty()) {
                            7、profiles是双端队列,处理每一个profile之前,都会先将其移除队
                               列,然后再载入这个profile对应的配置文件,从而得到一个当前
                               profile对应的配置源。
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
                            8、载入当前profile对应的配置源,然后将其添加到当前Loader实例的 
                               loaded map中。
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));

                        9、将已经载入的profile对应的配置源添加到环境实例中。
						addLoadedPropertySources();

                        10、然后修改环境中的激活profile列表。
						applyActiveProfiles(defaultProperties);
					});
		}


初始化profile 实现:
		private void initializeProfiles() {
			// The default profile for these purposes is represented as null. We add it
			// first so that it is processed first and has lowest priority.
            1、先添加一个null到当前Loader实例的profiles中,这个null profile对应的资源文件就是application.properties\yml这就是spring boot 会默认载入名称是application的资源文件的原因。
			this.profiles.add(null);
			Binder binder = Binder.get(this.environment);

            2、获取在环境实例准备阶段激活的profile,比如通过命令行激活的profile。
			Set<Profile> activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);

            3、获取在环境实例准备阶段激活的配置子项include,比如通过命令行激活的include。
			Set<Profile> includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);

            4、在获取其他激活的profile,比如我们通过代码方式设置的profile,为什么需要这一步呢,
那是因为2、3步骤都只是通过spring.profiles.active、spring.profiles.include去获取profile,而不
是直接从环境实例中获取激活的profile列表,因为环境实例中获取激活的profile列表可能会包含我们通过
code的方式添加的profile。
			List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
            
            5、将获取到其他方式激活的profile添加到当前Loader实例的双端队列profiles中。
			this.profiles.addAll(otherActiveProfiles);
			// Any pre-existing active profiles set via property sources (e.g.
			// System properties) take precedence over those added in config files.

            6、将通过使用配置项名称=spring.profiles.include获取到的配置子项添加到当前Loader实
               例的双端队列profiles中。
			this.profiles.addAll(includedViaProperty);

            7、使用通过配置项名称=spring.profiles.active获取到的激活profile添加到当前Loader实例的双端队列profiles中,这里很重要,情况如下:
                        1、spring.profiles.active激活的环境不为空,那就添加到当前Loader实例 
                           的双端队列profiles中,并且会将activatedProfiles属性修改为true,改
                           为true就表示就算在载入配置文件的时候,配置文件中也配置了
                           spring.profiles.active的话,那么将不会被激活。
                        2、spring.profiles.active激活的环境是空,那就不做任何事情,直接返回,
                           这个时候activatedProfiles属性还是false,那么当载入配置文件的时候,
                           配置文件中也配置了spring.profiles.active的话,就会被激活。
			addActiveProfiles(activatedViaProperty);

            8、如果最后发现当前的Loader实例的profiles只有一个,那就获取环境中默认的profile列表,然后构建成默认的profile添加到Loader实例的profiles中。
			if (this.profiles.size() == 1) { // only has null profile
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					this.profiles.add(defaultProfile);
				}
			}
		}

 在前面我们说了配置方式总结,这些规则完全是在源码中有体现的,ConfigFileApplicationListener 的原理就是这样,其实发现不算复杂,但是很重要,因为在开发过程中,如何正确的进行项目配置是一个基本功。

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值