springBoot源码分析如何加载配置文件

前言:springBoot的版本是  2.2.4.RELEASE

一、入口

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	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);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

查看配置文件主要是看 prepareEnvironment() 这个方法,即下面这个方法

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

二、prepareEnvironment 方法实现

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       ApplicationArguments applicationArguments) {
        // Create and configure the environment
        // 创建并配置相应的环境
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 根据用户配置,配置 environment 系统环境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        // 启动对应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载配置文件的监听器    
        //通过这个 ConfigFileApplicationListener 监听器就可以把 application.properties 或者application.yml 的配置信息给加载到容器中
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

2.1 第一行:getOrCreateEnvironment 方法实现

引:根据 webApplicationType 确定应用容器环境 Servlet

//根据 webApplicationType 确定应用容器环境 Servlet
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        // 如果应用类型是 SERVLET,则实例化 StandardServletEnvironment
        switch (this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
        }
    }

2.2  第二行:configureEnvironment 方法实现

 /**
     * Template method delegating to
     * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
     * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
     * Override this method for complete control over Environment customization, or one of
     * the above for fine-grained control over property sources or profiles, respectively.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureProfiles(ConfigurableEnvironment, String[])
     * @see #configurePropertySources(ConfigurableEnvironment, String[])
     */
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        // 将 main 函数的 args 封装成 SimpleCommandLinePropertySource 加入环境中
        configurePropertySources(environment, args);
        // 激活相应的配置文件
        configureProfiles(environment, args); //跟项目的多个运行环境有关,如:test\dev\prod 等
    }

2.3 第三行: attach 方法实现

该方法不是重点,暂时不分析

2.4 第四行:environmentPrepared 方法实现,最重要的方法

引:监听器的遍历

void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

2.4.1  继续跟踪 environmentPrepared 方法实现

注:environmentPrepared 方法是  SpringApplicationRunListener 的一个默认接口

截图:

 代码:

/**
	 * Called once the environment has been prepared, but before the
	 * {@link ApplicationContext} has been created.
	 * @param environment the environment
	 */
	default void environmentPrepared(ConfigurableEnvironment environment) {
	}

查看该接口的具体实现类:EventPublishingRunListener

截图:

 代码:

@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

这里有个初始化传播性事件接口,接着继续跟踪这个 multicastEvent 方法的具体实现,该接口是

SimpleApplicationEventMulticaster的方法

截图:

 代码:

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

接着继续查看 multicastEvent 方法实现

截图:

代码:

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

 继续查看 invokeListener 方法实现

/**
	 * Invoke the given listener with the given event.
	 * @param listener the ApplicationListener to invoke
	 * @param event the current event to propagate
	 * @since 4.1
	 */
	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);
		}
	}

在这个方法中可以看到 以 do 开头的方法,在 springBoot中以 do 开头的方法一般都是真正干苦力的方法,继续接着看 doInvokeListener 方法实现

@SuppressWarnings({"rawtypes", "unchecked"})
	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.isTraceEnabled()) {
					logger.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

这里有个  onApplicationEvent 应用发布事件,这是一个接口,具体找到配置文件的实现类,启动相应的监听器,EventPubshiingListener通知其他的监听器,这里会通知ConfigFileApplicationListener这个监听器来加载环境中的配置文件

截图:

查看该方法有哪些实现类,找到 ConfigFileApplicationListener 这个具体的实现类

截图:

 ConfigFileApplicationListener 具体的实现类

查看第一个 onApplicationEnvironmentPreparedEvent 方法,应用程序环境准备事件

 继续查看红框 postProcessEnvironment 方法实现

 这个方法点过来是一个接口,继续看这个接口的具体实现,还是回到具体实现类 ConfigFileApplicationListener

截图:

 实现类代码:

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}

 继续跟踪  addPropertySources 方法实现

截图:

 代码:

/**
	 * Add config file property sources to the specified environment.
	 * @param environment the environment to add source to
	 * @param resourceLoader the resource loader
	 * @see #addPostProcessors(ConfigurableApplicationContext)
	 */
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        //添加与随机数相关的配置源
		RandomValuePropertySource.addToEnvironment(environment);
        //Load 类最终负责加载配置文件
		new Loader(environment, resourceLoader).load();
	}

 Loader类是 ConfigFileApplicationListener 内部类,查看下 Loader 的构造方法

截图:

代码:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
			this.environment = environment;
			this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
			this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
            //从 spring.factories 文件中加载 PropertySourceLoader
            //PropertySourceLoader 有两个实现类:PropertiesPropertySourceLoader 和
            //YamlPropertySourceLoader,分别用于加载文件名后缀为 Properties 和 yaml 的文件
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
					getClass().getClassLoader());
		}

 接着看 load 方法的实现

截图:

 

代码:

void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
                        //initializePropfiles 从多个配置源加载设置的 profile,
                        //配置源可以是:环境变量、启动参数"--"设置、Environment对象设置等
                        //可以通过属性名 spring.profiles.include或 spring.profiles.active指定profile
                        //无论上述配置源有没有设置profile,都会在profiles属性中增加null,
                        //这是为了保证能首先处理默认的配置文件
						initializeProfiles();
                        //遍历 profile 
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
                            //读取配置文件,下面分析该方法
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
                        //读取application.properties 配置文件
                        //如果 application.properties 中没有spring.profiles属性,那么下面这个方法不会加载任何内容
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                        //将配置文件作为配置源添加到 Environment 对象中
                        //以后获取配置可以通过 Environment 获取
						addLoadedPropertySources();
                        //将 profile 设置到 Environment 对象中
						applyActiveProfiles(defaultProperties);
					});
		}

 

接着看红框的方法 load 的实现

截图:

 

 

代码:

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            //getSearchLocations()方法获得加载配置文件的路径
            //然后遍历这些路径
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
                //查找配置文件名,可以通过 spring.config.name 指定文件名
                //如果没有配置,使用默认名 application
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
                //接着load方法实现
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

 接着上面红框1中 getSearchLocations 方法的实现:

截图:

代码:

//获取加载配置文件的路径
//可以通过spring.config.location配置设置路径,如果没有配置,则使用默认
//默认路径由 DEFAULT_SEARCH_LOCATIONS 指定:
//String DEFAULT_SEARCH_LOCATIONS ="classpath:/,classpath:/config/,file:./,file:/config/"
private Set<String> getSearchLocations() {
			if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
				return getSearchLocations(CONFIG_LOCATION_PROPERTY);
			}
			Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
			locations.addAll(
					asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
			return locations;
		}

  接着上面红框2中load方法的实现:

截图:

 

 

 代码:

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
            //下面的if分支默认是不走的,除非我们设置spring.config.name为空或者null
            //或者是spring.config.location指定了配置文件的完整路径,也就是入参location的值
			if (!StringUtils.hasText(name)) {
				for (PropertySourceLoader loader : this.propertySourceLoaders) {
                    //检查配置文件名的后缀是否符合要求
                    //文件名后缀要求是properties、xml、yml或者yaml
					if (canLoadFileExtension(loader, location)) {
                        //加载location指定的文件,下面的load方法不做介绍
                        //其原理和下面将要调用的 loadForFileExtension 方法类似
						load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
						return;
					}
				}
				throw new IllegalStateException("File extension of config file location '" + location
						+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
						+ "a directory, it must end in '/'");
			}
			Set<String> processed = new HashSet<>();
            //properSourceLoaders 属性是在 Load 类的构造方法中设置的,可以加载文件后缀为 properties、xml、yml或者yaml的配置文件
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
                //获取文件拓展名进行遍历
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
                        //将路径、文件名、后缀名合起来形成完整文件名
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}

接着看截图红框中的方法  loadForFileExtension的实现

截图:

 

代码:

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
                //在文件名上加上 profile 值,之后调用load方法加载配置文件,入参带有过滤器,可以防止重复加载
				// Try profile-specific file & profile section in profile file (gh-340)
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
            //加载不带 profile 的配置文件
			// Also try the profile-specific section (if any) of the normal file
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

 接着看红框的 load 实现方法

截图:

 

代码:

//加载配置文件
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
			try {
                //调用 Resource 类加载配置文件
				Resource resource = this.resourceLoader.getResource(location);
				if (resource == null || !resource.exists()) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped missing config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped empty config extension ", location,
								resource, profile);
						this.logger.trace(description);
					}
					return;
				}
				String name = "applicationConfig: [" + location + "]";
                //读取配置文件内容,将其封装到 Document 类中,解析文件内容主要是找到
                //配置 spring.profiles.active 和 spring.profiles.include 的值
				List<Document> documents = loadDocuments(loader, name, resource);
                //如果文件没有配置数据,则跳过
				if (CollectionUtils.isEmpty(documents)) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				List<Document> loaded = new ArrayList<>();
                //遍历配置文件,处理里面配置的 profile
				for (Document document : documents) {
					if (filter.match(document)) {
                        //将配置文件中配置的 spring.profiles.active 和
                        //spring.profiles.include 的值写入集合 profiles 中
                        //上层调用方法会读取 profiles 集合中的值,并读取对应的配置文件
                        //addActiveProfiles 方法只在第一次调用时会其作用,里面有判断
						addActiveProfiles(document.getActiveProfiles());
						addIncludedProfiles(document.getIncludeProfiles());
						loaded.add(document);
					}
				}
				Collections.reverse(loaded);
				if (!loaded.isEmpty()) {
					loaded.forEach((document) -> consumer.accept(profile, document));
					if (this.logger.isDebugEnabled()) {
						StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
						this.logger.debug(description);
					}
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
			}
		}

 

 参考文章:https://www.cnblogs.com/fnlingnzb-learner/p/16800570.html

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值