• SpringBoot读取配置文件源码探究


    1. SpringBoot读取配置文件源码探究

    1.1. 概览

    • springboot的源码是再原来的Spring源码上又包了一层,看过spring源码都知道,当我们从入口debug进去的时候,原来的Spring源码都集中在refreshContext方法,SpringBoot的主要运行步骤,基本都包含在这个方法里了,而这个方法就是我们运行Springboot的主函数SpringApplication.run(Application.class, args);经过几步后到达的
    	public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		FailureAnalyzers analyzers = null;
    		configureHeadlessProperty();
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    					args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners,
    					applicationArguments);
    			Banner printedBanner = printBanner(environment);
    			context = createApplicationContext();
    			analyzers = new FailureAnalyzers(context);
    			prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			listeners.finished(context, null);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass)
    						.logStarted(getApplicationLog(), stopWatch);
    			}
    			return context;
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, listeners, analyzers, ex);
    			throw new IllegalStateException(ex);
    		}
    	}
    

    1.2. 配置读取步骤

    1.2.1. prepareEnvironment

    • 配置读取的步骤主要就在ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);这一步,我们继续深入
    	private ConfigurableEnvironment prepareEnvironment(
    			SpringApplicationRunListeners listeners,
    			ApplicationArguments applicationArguments) {
    		// Create and configure the environment
    		ConfigurableEnvironment environment = getOrCreateEnvironment();
    		configureEnvironment(environment, applicationArguments.getSourceArgs());
    		//主要是这步
    		listeners.environmentPrepared(environment);
    		if (!this.webEnvironment) {
    			environment = new EnvironmentConverter(getClassLoader())
    					.convertToStandardEnvironmentIfNecessary(environment);
    		}
    		return environment;
    	}
    
    • 创建基本的环境容器后,进入listeners.environmentPrepared(environment);通过监听器来进行环境变量的初始化,同时读取配置也是一部分工作

    1.2.2. environmentPrepared

    • 下一步,看到对监听器进行循环处理,这里的listeners中,默认只有一个EventPublishRunListener
    	public void environmentPrepared(ConfigurableEnvironment environment) {
    		for (SpringApplicationRunListener listener : this.listeners) {
    			listener.environmentPrepared(environment);
    		}
    	}
    
    • 继续如下,这一步它进行广播事件了
    	@Override
    	public void environmentPrepared(ConfigurableEnvironment environment) {
    		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
    				this.application, this.args, environment));
    	}
    

    1.2.3. multicastEvent

    	@Override
    	public void multicastEvent(ApplicationEvent event) {
    		multicastEvent(event, resolveDefaultEventType(event));
    	}
    

    继续,重点是invokeListener方法,去调用监听器事件,可以想象对配置文件来讲,这就是读取配置事件了。同时监听器有很多,读取配置文件的监听器是ConfigFileAplicationListener,看名字还是蛮明显的吧
    image.png

    	@Override
    	public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    			Executor executor = getTaskExecutor();
    			if (executor != null) {
    				executor.execute(new Runnable() {
    					@Override
    					public void run() {
    						invokeListener(listener, event);
    					}
    				});
    			}
    			else {
    				//重点
    				invokeListener(listener, event);
    			}
    		}
    	}
    

    继续,和上一步类似,do真正的事件了

    	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    		ErrorHandler errorHandler = getErrorHandler();
    		if (errorHandler != null) {
    			try {
    				doInvokeListener(listener, event);
    			}
    			catch (Throwable err) {
    				errorHandler.handleError(err);
    			}
    		}
    		else {
    			//重点
    			doInvokeListener(listener, event);
    		}
    	}
    
    	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    		try {
    			// 入口
    			listener.onApplicationEvent(event);
    		}
    		catch (ClassCastException ex) {
    			String msg = ex.getMessage();
    			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
    				// Possibly a lambda-defined listener which we could not resolve the generic event type for
    				// -> let's suppress the exception and just log a debug message.
    				Log logger = LogFactory.getLog(getClass());
    				if (logger.isDebugEnabled()) {
    					logger.debug("Non-matching event type for listener: " + listener, ex);
    				}
    			}
    			else {
    				throw ex;
    			}
    		}
    	}
    

    1.2.4. onApplicationEvent

    • ConfigFileApplicationListener类中继续
    	@Override
    	public void onApplicationEvent(ApplicationEvent event) {
    		if (event instanceof ApplicationEnvironmentPreparedEvent) {
    			//配置入口
    			onApplicationEnvironmentPreparedEvent(
    					(ApplicationEnvironmentPreparedEvent) event);
    		}
    		if (event instanceof ApplicationPreparedEvent) {
    			onApplicationPreparedEvent(event);
    		}
    	}
    

    继续,可以看到处理器有这些,我们关注ConfigFileApplicationListener
    image.png

    	private void onApplicationEnvironmentPreparedEvent(
    			ApplicationEnvironmentPreparedEvent event) {
    		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    		postProcessors.add(this);
    		AnnotationAwareOrderComparator.sort(postProcessors);
    		for (EnvironmentPostProcessor postProcessor : postProcessors) {
    			//入口
    			postProcessor.postProcessEnvironment(event.getEnvironment(),
    					event.getSpringApplication());
    		}
    	}
    

    1.2.5. postProcessEnvironment

    	@Override
    	public void postProcessEnvironment(ConfigurableEnvironment environment,
    			SpringApplication application) {
    		//重点入口
    		addPropertySources(environment, application.getResourceLoader());
    		configureIgnoreBeanInfo(environment);
    		bindToSpringApplication(environment, application);
    	}
    

    继续

    	protected void addPropertySources(ConfigurableEnvironment environment,
    			ResourceLoader resourceLoader) {
    		RandomValuePropertySource.addToEnvironment(environment);
    		//总算看到加载入口了
    		new Loader(environment, resourceLoader).load();
    	}
    

    1.2.6. load

    public void load() {
    			this.propertiesLoader = new PropertySourcesLoader();
    			this.activatedProfiles = false;
    			this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
    			this.processedProfiles = new LinkedList<Profile>();
    
    			// Pre-existing active profiles set via Environment.setActiveProfiles()
    			// are additional profiles and config files are allowed to add more if
    			// they want to, so don't call addActiveProfiles() here.
    			Set<Profile> initialActiveProfiles = initializeActiveProfiles();
    			this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
    			if (this.profiles.isEmpty()) {
    				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
    					Profile defaultProfile = new Profile(defaultProfileName, true);
    					if (!this.profiles.contains(defaultProfile)) {
    						this.profiles.add(defaultProfile);
    					}
    				}
    			}
    
    			// The default profile for these purposes is represented as null. We add it
    			// last so that it is first out of the queue (active profiles will then
    			// override any settings in the defaults when the list is reversed later).
    			this.profiles.add(null);
    
    			while (!this.profiles.isEmpty()) {
    				Profile profile = this.profiles.poll();
    				for (String location : getSearchLocations()) {
    					if (!location.endsWith("/")) {
    						// location is a filename already, so don't search for more
    						// filenames
    						load(location, null, profile);
    					}
    					else {
    						for (String name : getSearchNames()) {
    							//加载入口
    							load(location, name, profile);
    						}
    					}
    				}
    				this.processedProfiles.add(profile);
    			}
    
    			addConfigurationProperties(this.propertiesLoader.getPropertySources());
    		}
    
    • 可以看到它的加载名从getSearchNames获取,那就看看这个方法
    		private Set<String> getSearchNames() {
    			if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
    				return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
    						null);
    			}
    			return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
    		}
    
    • CONFIG_NAME_PROPERTY值为spring.config.nameDEFAULT_NAMES值为application,所以可以看出application这个名字就是默认的配置名了,但也可以用spring.config.name属性来修改

    • 其实到这一步,后面已经没难度了,可以想象,接下去应该是拼接出完整的路径,找到文件读取,还是走完流程把

    • 继续

    		private void load(String location, String name, Profile profile) {
    			String group = "profile=" + ((profile != null) ? profile : "");
    			if (!StringUtils.hasText(name)) {
    				// Try to load directly from the location
    				loadIntoGroup(group, location, profile);
    			}
    			else {
    				// Search for a file with the given name
    				for (String ext : this.propertiesLoader.getAllFileExtensions()) {
    					if (profile != null) {
    						// Try the profile-specific file
    						loadIntoGroup(group, location + name + "-" + profile + "." + ext,
    								null);
    						for (Profile processedProfile : this.processedProfiles) {
    							if (processedProfile != null) {
    								loadIntoGroup(group, location + name + "-"
    										+ processedProfile + "." + ext, profile);
    							}
    						}
    						// Sometimes people put "spring.profiles: dev" in
    						// application-dev.yml (gh-340). Arguably we should try and error
    						// out on that, but we can be kind and load it anyway.
    						loadIntoGroup(group, location + name + "-" + profile + "." + ext,
    								profile);
    					}
    					// Also try the profile-specific section (if any) of the normal file
    					//加载重点
    					loadIntoGroup(group, location + name + "." + ext, profile);
    				}
    			}
    		}
    
    • 继续
    		private PropertySource<?> loadIntoGroup(String identifier, String location,
    				Profile profile) {
    			try {
    				//入口
    				return doLoadIntoGroup(identifier, location, profile);
    			}
    			catch (Exception ex) {
    				throw new IllegalStateException(
    						"Failed to load property source from location '" + location + "'",
    						ex);
    			}
    		}
    
    • do开头都是正式要干事了
    		private PropertySource<?> doLoadIntoGroup(String identifier, String location,
    				Profile profile) throws IOException {
    			Resource resource = this.resourceLoader.getResource(location);
    			PropertySource<?> propertySource = null;
    			StringBuilder msg = new StringBuilder();
    			if (resource != null && resource.exists()) {
    				String name = "applicationConfig: [" + location + "]";
    				String group = "applicationConfig: [" + identifier + "]";
    				// 加载入口
    				propertySource = this.propertiesLoader.load(resource, group, name,
    						(profile != null) ? profile.getName() : null);
    				if (propertySource != null) {
    					msg.append("Loaded ");
    					handleProfileProperties(propertySource);
    				}
    				else {
    					msg.append("Skipped (empty) ");
    				}
    			}
    			else {
    				msg.append("Skipped ");
    			}
    			msg.append("config file ");
    			msg.append(getResourceDescription(location, resource));
    			if (profile != null) {
    				msg.append(" for profile ").append(profile);
    			}
    			if (resource == null || !resource.exists()) {
    				msg.append(" resource not found");
    				this.logger.trace(msg);
    			}
    			else {
    				this.logger.debug(msg);
    			}
    			return propertySource;
    		}
    
    • load内容,这里有两个加载器,看名字也知道了,yml结尾的文件肯定用YamlPropertySourceLoader才能加载,properties结尾的用另一个
      image.png
    	public PropertySource<?> load(Resource resource, String group, String name,
    			String profile) throws IOException {
    		if (isFile(resource)) {
    			String sourceName = generatePropertySourceName(name, profile);
    			for (PropertySourceLoader loader : this.loaders) {
    				if (canLoadFileExtension(loader, resource)) {
    					// 干事的入口
    					PropertySource<?> specific = loader.load(sourceName, resource,
    							profile);
    					addPropertySource(group, specific);
    					return specific;
    				}
    			}
    		}
    		return null;
    	}
    
    • 我用的是yml,所以在YamlPropertySourceLoader
    	@Override
    	public PropertySource<?> load(String name, Resource resource, String profile)
    			throws IOException {
    		if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
    			Processor processor = new Processor(resource, profile);
    			//真正的处理类
    			Map<String, Object> source = processor.process();
    			if (!source.isEmpty()) {
    				return new MapPropertySource(name, source);
    			}
    		}
    		return null;
    	}
    
    		public Map<String, Object> process() {
    			final Map<String, Object> result = new LinkedHashMap<String, Object>();
    			//接近了
    			process(new MatchCallback() {
    				@Override
    				public void process(Properties properties, Map<String, Object> map) {
    					result.putAll(getFlattenedMap(map));
    				}
    			});
    			return result;
    		}
    

    1.2.7. process

    	protected void process(MatchCallback callback) {
    		Yaml yaml = createYaml();
    		for (Resource resource : this.resources) {
    			//更近了
    			boolean found = process(callback, yaml, resource);
    			if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
    				return;
    			}
    		}
    	}
    
    • 继续深入
      image.png
      image.png
    • 可以看到,总算把我配合文件的内容给读到了,然后放入callback
    	private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
    		int count = 0;
    		try {
    			if (logger.isDebugEnabled()) {
    				logger.debug("Loading from YAML: " + resource);
    			}
    			//读取文件
    			Reader reader = new UnicodeReader(resource.getInputStream());
    			try {
    				for (Object object : yaml.loadAll(reader)) {
    					//总算拿到了
    					if (object != null && process(asMap(object), callback)) {
    						count++;
    						if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
    							break;
    						}
    					}
    				}
    				if (logger.isDebugEnabled()) {
    					logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") +
    							" from YAML resource: " + resource);
    				}
    			}
    			finally {
    				reader.close();
    			}
    		}
    		catch (IOException ex) {
    			handleProcessError(resource, ex);
    		}
    		return (count > 0);
    	}
    
    • 结果就存入了result

    image.png

    • 这样之后返回去看,你就会看到它存入了MapPropertySource属性资源,在之后就会被用上了

    1.3. 总结

    • 我通过一步步的代码跟踪,解析了SpringBoot读取application.yml的整个流程,代码虽然贴的比较多,但可以让初学者也可以跟着这个步骤完整的理解一遍,代码中的关键步骤我都用中文标明了,其它没标注部分不是这章的重点,想研究的自行研究
  • 相关阅读:
    Dart语言--基础内容
    Vuex入门介绍
    videojs中文文档详解
    在Vue中如何使用axios请求拦截
    vue监听滚动事件,实现滚动监听(scroll滚动)
    font-family:中文字体的英文名称 (宋体 微软雅黑)
    VUE项目中按需引入ECharts.js
    VUE 重载组件
    【Web】网站主如何更改网页标签的图标(favicon.ico)
    我的电脑cmd命令引入sql数据库
  • 原文地址:https://www.cnblogs.com/sky-chen/p/11436466.html
Copyright © 2020-2023  润新知