前言: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 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 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 getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set 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 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 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 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