SpringBoot啟動流程SpringApplication準備階段源碼分析
SpringBoot啟動流程源碼分析一、入口參數(shù)研究和創(chuàng)建對象
我們在上一篇的時候主要針對入口參數(shù)和SpringApplication創(chuàng)建對象的過程進行了研究,本篇主要針對執(zhí)行run方法之前的準備過程進行研究。
準備階段分析
以下先看下SpringApplication的run()方法
package org.springframework.boot;
public ConfigurableApplicationContext run(String... args) {
//1.計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//2.headless配置
configureHeadlessProperty();
//3、獲取監(jiān)聽
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//應用程序啟動的參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//4、準備環(huán)境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//環(huán)境創(chuàng)建成功后,配置bean信息,決定是否跳過 BeanInfo 類的掃描,如果設置為 true,則跳過
configureIgnoreBeanInfo(environment);
//打印banner信息
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();
//控制是否打印日志的,這里為true,即打印日志
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;
}
我將會根據(jù)執(zhí)行過程逐行進行分析
1、StopWatch計時器
此類實則為計時器,如下對具體使用進行分析
StopWatch stopWatch = new StopWatch(); //開始計時 stopWatch.start(); //停止計時 stopWatch.stop();
對于具體打印的上面寫的為
//將當前類傳入StartupInfoLogger創(chuàng)建了一個對象
//然后調(diào)用logStarted打印日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
//創(chuàng)建一個Log類
protected Log getApplicationLog() {
if (this.mainApplicationClass == null) {
return logger;
}
return LogFactory.getLog(this.mainApplicationClass);
}
//調(diào)用log類的log.info()方法來打印日志
public void logStarted(Log log, StopWatch stopWatch) {
if (log.isInfoEnabled()) {
log.info(getStartedMessage(stopWatch));
}
}
//打印詳細的日志
private StringBuilder getStartedMessage(StopWatch stopWatch) {
StringBuilder message = new StringBuilder();
message.append("Started ");
message.append(getApplicationName());
message.append(" in ");
message.append(stopWatch.getTotalTimeSeconds());
try {
double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
message.append(" seconds (JVM running for " + uptime + ")");
}
catch (Throwable ex) {
// No JVM time available
}
return message;
}
這里可以看到stopWatch.getTotalTimeSeconds()方法就是來獲取實際的計時時間的。再者,通過這幾行代碼,我們也可以考慮下平常在寫代碼的時候,有幾種日志打印方式?SpringBoot是怎么集成日志框架的?
2、configureHeadlessProperty()
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
這一部分代碼這樣理解吧,首先java.awt包提供了用于創(chuàng)建用戶界面和繪制圖形圖像的所有分類,那么 屬性SYSTEM_PROPERTY_JAVA_AWT_HEADLESS就一定會和用戶界面相關了。 這里將SYSTEM_PROPERTY_JAVA_AWT_HEADLESS設置為true,其實就是表示在缺少顯示屏、鍵盤或者鼠標中的系統(tǒng)配置,如果將其設置為true,那么headless工具包就會被使用。
3、getRunListeners(args) 獲取監(jiān)聽
總體上可以分這三步
- 獲取一個默認的加載器
- 根據(jù)類型獲取spring.factories中符合的類名
- 創(chuàng)建類實例,返回
如下將跟下代碼
//獲取所有監(jiān)聽 SpringApplicationRunListeners listeners = getRunListeners(args); //啟動監(jiān)聽 listeners.starting();
跳轉進入getRunListeners方法
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//3.1獲取類加載器
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//3.2 根據(jù)類型獲取spring.factories中符合的類名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//3.3 創(chuàng)建類實例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//對實例進行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringApplicationRunListeners類解讀
先看下SpringApplicationRunListeners類
/**
* A collection of {@link SpringApplicationRunListener}.
*
* @author Phillip Webb
*/
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
SpringApplicationRunListeners類內(nèi)部關聯(lián)了SpringApplicationRunListener的集合,說白了就是用List集合存儲了SpringApplicationRunListeners類,那么,我們就需要了解一下這個類是干嘛的
老規(guī)矩,先把源碼抬上來
/**
*//可以理解為Spring Boot應用的運行時監(jiān)聽器
* Listener for the {@link SpringApplication} {@code run} method.
*//SpringApplicationRunListener的構造器參數(shù)必須依次為SpringApplication和String[]類型
* {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
* and should declare a public constructor that accepts a {@link SpringApplication}
* instance and a {@code String[]} of arguments.
*//每次運行的時候將會創(chuàng)建一個 SpringApplicationRunListener
A new
* {@link SpringApplicationRunListener} instance will be created for each run.
*
*/
public interface SpringApplicationRunListener {
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
*/
//Spring應用剛啟動
void starting();
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
*/
//ConfigurableEnvironment準備妥當,允許將其調(diào)整
void environmentPrepared(ConfigurableEnvironment environment);
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
//ConfigurableApplicationContext準備妥當,允許將其調(diào)整
void contextPrepared(ConfigurableApplicationContext context);
/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
//ConfigurableApplicationContext已裝載,但是任未啟動
void contextLoaded(ConfigurableApplicationContext context);
/**
* The context has been refreshed and the application has started but
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @since 2.0.0
*/
//ConfigurableApplicationContext已啟動,此時Spring Bean已初始化完成
void started(ConfigurableApplicationContext context);
/**
* Called immediately before the run method finishes, when the application context has
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @since 2.0.0
*/
//Spring應用正在運行
void running(ConfigurableApplicationContext context);
/**
* Called when a failure occurs when running the application.
* @param context the application context or {@code null} if a failure occurred before
* the context was created
* @param exception the failure
* @since 2.0.0
*/
//Spring應用運行失敗
void failed(ConfigurableApplicationContext context, Throwable exception);
}
單純的看源碼,是一個簡單的接口,這時候我們可以看下作者給的注釋。理解部分就直接加到上面源碼中了。
再看下他的實現(xiàn)類EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
這里我們看到兩點:
- 構造器參數(shù)和他實現(xiàn)的接口(上面剛分析了)注釋中規(guī)定的一致
- 將SpringApplication中的ApplicationListener實例列表全部添加到了SimpleApplicationEventMulticaster對象中
SimpleApplicationEventMulticaster是Spring框架的一個監(jiān)聽類,用于發(fā)布Spring應用事件。因此EventPublishingRunListener實際充當了Spring Boot事件發(fā)布者的角色。
這里我再跟進源碼的時候發(fā)現(xiàn),針對SpringBoot的事件/監(jiān)聽機制內(nèi)容還是挺多的,我們在充分理解的時候需要先了解Spring的事件/監(jiān)聽機制,后面將兩個結合后單獨進行對比分析。
3.1獲取類加載器getClassLoader()
ClassLoader classLoader = getClassLoader();
public ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
這里的類加載器獲取首先是獲取resourceLoader的類加載器,獲取不到則獲取默認的類加載器。 resourceLoader是資源加載器類,有具體的實現(xiàn)類。
3.2 根據(jù)類型獲取spring.factories中符合的類名
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//獲取類型名稱:org.springframework.context.ApplicationContextInitializer
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
我們繼續(xù)對loadSpringFactories追下去
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//從緩存里面獲取
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//執(zhí)行classLoader.getResources("META-INF/spring.factories"),表示通過加載器獲取META-INF/spring.factories下的資源
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
//文件地址
URL url = (URL)urls.nextElement();
//從指定位置加載UrlResource
UrlResource resource = new UrlResource(url);
//加載里面的屬性,屬性見下圖
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
//獲取key值
String factoryClassName = ((String)entry.getKey()).trim();
//獲取value值
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
//這里是將查詢出來的key作為result的key,value轉換成字符數(shù)組存放到result的value中
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
//將結果集存入緩存中
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}

default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
這個的意思是如果沒有,則獲取一個空的list
3.3創(chuàng)建實例createSpringFactoriesInstances()
這一步其實就是將上一步從META-INF/spring.factories加載進來的資源進行實例化。
private <T> List<T> createSpringFactoriesInstances()(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//根據(jù)類加載器獲取指定類
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
//根據(jù)參數(shù)獲取構造器
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
//根據(jù)傳入的構造器對象以及構造器所需的參數(shù)創(chuàng)建一個實例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
//添加實例到集合中
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
4、環(huán)境準備prepareEnvironment
prepareEnvironment(listeners, applicationArguments)
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//4.1 創(chuàng)建一個環(huán)境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//4.2 配置環(huán)境
configureEnvironment(environment, applicationArguments.getSourceArgs());
//4.3 ConfigurationPropertySourcesPropertySource對象存入到第一位
ConfigurationPropertySources.attach(environment);
//listeners環(huán)境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)
listeners.environmentPrepared(environment);
// 將環(huán)境綁定到SpringApplication
bindToSpringApplication(environment);
// 如果是非web環(huán)境,將環(huán)境轉換成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 配置PropertySources對它自己的遞歸依賴
ConfigurationPropertySources.attach(environment);
return environment;
}
4.1創(chuàng)建一個環(huán)境getOrCreateEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
//有的話,直接返回
if (this.environment != null) {
return this.environment;
}
//這里我們在上面見到過,通過WebApplicationType.deduceFromClasspath()方法獲取的
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
這里創(chuàng)建了一個StandardServletEnvironment實例的環(huán)境 systemProperties用來封裝了JDK相關的信息 如下圖

systemEnvironment用來封轉環(huán)境相關的信息

封裝的還是挺詳細的哈。
4.2 配置環(huán)境
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
setConversionService(ConfigurableConversionService conversionService)方法繼承于ConfigurablePropertyResolver接口, 該接口是PropertyResolver類型都將實現(xiàn)的配置接口。提供用于訪問和自定義將屬性值從一種類型轉換為另一種類型時使用的ConversionService的工具。PropertyResolver是用于針對任何底層源解析屬性的接口。
configurePropertySources(environment, args);當前方法主要是將啟動命令中的參數(shù)和run 方法中的參數(shù)封裝為PropertySource。
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
//獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
//是否添加命令啟動參數(shù),addCommandLineProperties為true,表示需要添加,但是前提是你得配置了參數(shù)
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
configureProfiles(environment, args);環(huán)境配置
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
//獲取激活的環(huán)境
environment.getActiveProfiles(); // ensure they are initialized
// But these ones should go first (last wins in a property key clash)
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
//設置當前的環(huán)境
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
4.3 ConfigurationPropertySourcesPropertySource對象存入
public static void attach(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
//獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性
MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
//判斷是否有 屬性 configurationProperties
PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
if (attached != null && attached.getSource() != sources) {
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
attached = null;
}
if (attached == null) {
// 將sources封裝成ConfigurationPropertySourcesPropertySource對象,并把這個對象放到sources的第一位置
sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
new SpringConfigurationPropertySources(sources)));
}
}
總結
準備階段主要干了如下幾件事情
- 設置headless為true(表示可以在缺少顯示屏、鍵盤或者鼠標時候的系統(tǒng)配置)
- 文件META-INF\spring.factories中獲取SpringApplicationRunListener接口的實現(xiàn)類EventPublishingRunListener,主要發(fā)布SpringApplicationEvent。
- 創(chuàng)建Environment并設置比如環(huán)境信息,系統(tǒng)屬性,輸入?yún)?shù)和profile等信息
- 打印Banner信息
本文僅為個人能力范圍內(nèi)理解,旨在分享出來和大家討論技術,共同努力,共同進步!
參考:《SpringBoot編程思想》
以上就是SpringBoot啟動流程SpringApplication準備階段源碼分析的詳細內(nèi)容,更多關于SpringBoot SpringApplication 啟動的資料請關注腳本之家其它相關文章!
相關文章
解析java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能
這篇文章主要為大家介紹了java稀疏數(shù)組如何幫助我們節(jié)省內(nèi)存提升性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
SpringBoot中實現(xiàn)定時任務的4種方式詳解
這篇文章主要介紹了SpringBoot中實現(xiàn)定時任務的4種方式詳解,在Springboot中定時任務是一項經(jīng)常能用到的功能,實現(xiàn)定時任務的方式有很多,今天來介紹常用的幾種,需要的朋友可以參考下2023-11-11
圖文教程教你IDEA中的Spring環(huán)境搭建+簡單入門
這篇文章主要介紹了圖文教程教你IDEA中的Spring環(huán)境搭建+簡單入門,Spring的環(huán)境搭建使用Maven更加方便,需要的朋友可以參考下2023-03-03

