Spring Boot
作為目前最流行的Java開發框架,秉承“約定優於配置”原則,大大簡化了Spring MVC
繁瑣的XML
檔案配置,基本實現零配置啟動專案。
本文基於
Spring Boot 2.1.0.RELEASE
版本瞭解Spring Boot
如何啟動
首先讓我們看一下最簡單的Spring Boot
啟動程式碼
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
複製程式碼
每一個使用過Spring Boot
的同學對於上面的程式碼應該都非常熟悉了,通過這段程式碼即可啟動Spring Boot
應用。那麼SpringApplication.run(DemoApplication.class, args)
內部到底做了什麼事情呢?
在檢視具體程式碼之前,我們先了解一下SpringApplication
內部大概的執行流程,如下圖
從上圖中可以看出run()
是整個應用的入口,接著初始化SpringApplicationRunListener
,Environment
等例項,然後建立應用上下文物件,“準備”並“重新整理”上下文,到這裡Spring
容器已基本啟動完成,最後傳送事件通知各個元件作出相應動作。
原始碼分析
在瞭解完大概的流程之後,下面開始深入原始碼分析Spring Boot
具體的啟動過程,首先進入入口方法run
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();
// ...
複製程式碼
StopWatch
主要是用來統計每項任務執行時長,例如Spring Boot
啟動佔用總時長。
Started DemoApplication in 4.241 seconds (JVM running for 5.987)
getRunListeners()
完成了SpringApplicationRunListener
例項化工作,如何完成的呢?進入方法內部檢視
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
複製程式碼
SpringApplicationRunListeners
和SpringApplicationRunListener
不是同一個類,它們名稱非常相似
檢視SpringApplicationRunListeners
原始碼
SpringApplicationRunListeners(Log log,
Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
public void starting() {
for (SpringApplicationRunListener listener : this.listeners {
listener.starting();
}
}
public void environmentPrepared() {
// ....
}
public void contextPrepared() {
// ....
}
public void contextLoaded() {
// ....
}
public void started() {
// ....
}
public void running() {
// ....
}
複製程式碼
它是
SpringApplicationRunListener
的一個集合
觀察SpringApplicationRunListeners
所有方法,可以看出,它實際是一個用來傳送SpringApplicationRunListener
相關事件的工具類
接著繼續觀察getSpringFactoriesInstances
原始碼,看它是如何例項化物件的(此方法後續多處使用)
private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 載入物件名稱
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type,classLoader));
List<T> instances = createSpringFactoriesInstances(type parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
這裡通過SpringFactoriesLoader.loadFactoryNames
獲取type
對應的FactoryNames
,不明白有什麼用處?進入方法內部檢視
public static List<String> loadFactoryNames(Class<?>factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefaul(factoryClassName, Collections.emptyList());
}
複製程式碼
繼續進入loadSpringFactories
方法內部
public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactorie(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.ge(classLoader);
if (result != null) {
return result;
}
try {
// 獲取 META-INF/spring.factories 對應的資源
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResource(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResource(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 讀取檔案內容
Properties properties =PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySe()) {
String factoryClassName = ((String)entry.getKey()).trim();
for (String factoryName :StringUtils.commaDelimitedListToStringArray(String) entry.getValue())) {
// 獲取 factoryClassName 對應的多個valu(多個value用逗號分隔)
result.add(factoryClassName,factoryName.trim());
}
}
}
// 快取已經讀取到的內容
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to loadfactories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
複製程式碼
看到這裡可能會疑惑META-INF/spring.factories
檔案在哪裡?檔案裡面有什麼內容?
其實這個檔案存放在Spring Boot
和Spring Boot autoconfigure
的jar包內部(有興趣的同學可以自行下載jar包並解壓檢視),Spring Boot
中的檔案內容如下:
# 完整內容請檢視原檔案
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製程式碼
可以看到SpringApplicationRunListener
對應的值是EventPublishingRunListener
回到SpringFactoriesLoader.loadFactoryNames
方法內部,可以發現方法獲取的值實際上是factoryClass
在META-INF/spring.factories
中對應的實現類的集合
明白這個方法之後,再回到getSpringFactoriesInstances
方法
private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 獲取 SpringApplicationRunListener 對應的實現類的名稱集合
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type,classLoader));
// 通過反射例項化物件
List<T> instances = createSpringFactoriesInstances(type parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
到此為止getRunListeners
完成了SpringApplicationRunListener
對應實現類的例項化,並回撥其starting
方法
SpringApplicationRunListeners listeners getRunListeners(args);
listeners.starting();
複製程式碼
從上面分析得知,實際上呼叫的是EventPublishingRunListener
的starting
方法,那麼方法內部做了什麼呢?
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application,this.args));
}
複製程式碼
傳送了一個ApplicationStartingEvent
事件
繼續查詢ApplicationStartingEvent
事件的消費者,從spring.factories
中可以找到所有預定義的事件消費者
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製程式碼
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
複製程式碼
接下來要做的就是從這些消費者中找出ApplicationStartingEvent
事件的消費者(查詢過程省略),找到以下兩個消費者
-
LoggingApplicationListener 初始化日誌系統
-
LiquibaseServiceLocatorApplicationListener (引數liquibase.servicelocator.ServiceLocator)如果存在,則使用springboot相關的版本進行替代
瞭解完ApplicationStartingEvent
事件之後,回到run
方法繼續往下探究prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 建立Environment物件
ConfigurableEnvironment environment =getOrCreateEnvironment();
configureEnvironment(environment,applicationArguments.getSourceArgs());
// 釋出ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverte(getClassLoader())
.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
複製程式碼
這裡又釋出了一個ApplicationEnvironmentPreparedEvent
事件,繼續查詢事件監聽物件
- FileEncodingApplicationListener
檢查系統檔案編碼格式是否符合環境變數中配置的檔案編碼格式(如果存在相關設定 - spring.mandatory-file-encoding),如果編碼不符合,則丟擲異常阻止
Spring
啟動 - AnsiOutputApplicationListener 是否開啟AnsiOutput
- DelegatingApplicationListener 代理context.listener.classes配置的監聽者
- ClasspathLoggingApplicationListener 日誌輸出classpath
- LoggingApplicationListener 配置日誌系統,logging.config, logging.level...等
- ConfigFileApplicationListener 這是一個比較重要的監聽物件,具體的方法實現如下
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor :postProcessors) {
postProcessor.postProcessEnvironmen(event.getEnvironment(),
event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactorie(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
複製程式碼
通過spring.factories
,可以看到這裡載入以下EnvironmentPostProcessor
物件
- CloudFoundryVcapEnvironmentPostProcessor
- SpringApplicationJsonEnvironmentPostProcessor
- SystemEnvironmentPropertySourceEnvironmentPostProcessor
- ConfigFileApplicationListener
很多同學可能會疑問ConfigFileApplicationListener
並不存在spring.factories
檔案中,這裡為什麼會有它呢?
實際上ConfigFileApplicationListener
在onApplicationEnvironmentPreparedEvent
方法中,將自身新增到EnvironmentPostProcessor
物件列表中。
我們主要關注ConfigFileApplicationListener
的postProcessEnvironment
方法
public void postProcessEnvironment(ConfigurableEnvironmentenvironment,
SpringApplication application) {
addPropertySources(environment,application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironmentenvironment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
// 讀取applicaiton.yml, application.properties等配置檔案
new Loader(environment, resourceLoader).load();
}
複製程式碼
ConfigFileApplicationListener
監聽到ApplicationEnvironmentPreparedEvent
事件之後開始讀取本地配置檔案
關於Spring
如何讀取本地配置檔案,請前往Spring Boot原始碼分析-配置檔案載入原理
建立ApplicationContext
物件
protected ConfigurableApplicationContextcreateApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根據webApplicationType建立對應上下文物件
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forNam(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forNam(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forNam(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a defaultApplicationContext, "
+ "please specify anApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
複製程式碼
這裡是根據webApplicationType
決定建立什麼型別的ApplicationContext
物件,那麼webApplicationType
是何時賦值的呢?
public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must notbe null");
this.primarySources = new LinkedHashSet<>(Arrays.asLis(primarySources));
// 初始化webApplicationType
this.webApplicationType =WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstance(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstance(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass(;
}
複製程式碼
從上面可以看出是通過WebApplicationType.deduceFromClasspath
方法初始化的webApplicationType
,繼續跟蹤程式碼
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
複製程式碼
從上面程式碼中可以看出Spring
是通過當前classpath
下是否存在相應的類,從而決定webApplicationType
型別
初始化ApplicationContext
物件
private void prepareContext(ConfigurableApplicationContextcontext,
ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, BannerprintedBanner) {
// 初始化context
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
// 傳送ApplicationContextInitializedEvent訊息
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory =context.getBeanFactory();
beanFactory.registerSingleto("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner",printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 註冊DemoApplication
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
複製程式碼
這裡註冊了DemoApplication
到Spring
容器中,為後續bean掃描做準備
接下來繼續深入refreshContext
方法,可以發現實際上是執行了AbstractApplicationContext.refresh
方法
public void refresh() throws BeansException,IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
// 完成bean的載入
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered duringcontext initialization - " +
"cancelling refresh attempt: " + ex;
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
複製程式碼
refresh
方法內部做了很多事情。比如:完成BeanFactory
設定,BeanFactoryPostProcessor
、BeanPostProcessor
介面回撥,Bean
載入,國際化配置等。
到此為止Spring
基本完成了容器的初始化工作,最後在呼叫callRunners
方法,執行ApplicationRunner
、CommandLineRunner
介面。
private void callRunners(ApplicationContext context,ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfTyp(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfTyp(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
複製程式碼
整個啟動過程的核心方法是refresh
,此方法內部承載大部分容器啟動所需的工作。由於篇幅原因,後續再進行refresh
內部原始碼分析,瞭解Spring Boot
載入Bean
的整個過程。