仍是從SpringBoot的啓動類提及,這篇文章主要分析啓動類中的SpringApplicationhtml
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製代碼
能夠看出main函數中重要的就是SpringApplication.run()
,這能夠分爲兩部分來探討:java
首先進入SpringApplication的構造函數,先是單個參數的構造方法,後進入兩個參數的構造方法,ResourceLoader是Spring的資源加載器,這裏沒有自定義的ResourceLoader傳入,因此爲NULL,而primarySources參數就是咱們傳入的Application.class啓動類react
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1. 推斷應用類型
this.webApplicationType = deduceWebApplicationType();
//2. initializer初始化模塊,加載ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//3. 加載監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4. 配置應用main方法所在的類
this.mainApplicationClass = deduceMainApplicationClass();
}
複製代碼
SpringApplication的初始化主要包括如下4個步驟:git
this.webEnvironment=deduceWebApplicationType(); 判斷應用的類型,是不是servlet應用仍是reactive應用或者是none,webEnvironment中定義了這三種類型 github
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 經過SpringFactoriesLoader在應用的classpath中查找並加載全部可用的ApplicationContextInitializer web
然後經過loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 從全部META-INF/spring.factories文件的配置信息的map中獲取指定的factory的值 spring
對於 ApplicationContextInitializer,它是應用程序初始化器,作一些初始化工做springboot
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/** * Initialize the given application context. * @param applicationContext the application to configure */
void initialize(C applicationContext);
}
複製代碼
setListeners()方法與setInitializers()方法相似,只不過它是使用SpringFactoriesLoader在應用的classpath的META-INT/spring.factories中查找並加載全部可用的ApplicationListenerbash
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
複製代碼
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
複製代碼
更詳細的分析能夠參閱我以前的文章: springboot系列文章之啓動時初始化數據網絡
在SpringApplication構造函數的最後一步,根據調用棧推斷並設置main方法的定義類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
複製代碼
SpringApplication實例初始化完成而且完成設置後,就能夠開始run方法的邏輯了,對於這個run方法我將分爲如下幾點進行逐步剖析,而StopWatch是一個工具類,主要是方便記錄程序運行時間,這裏就不仔細介紹了。
public ConfigurableApplicationContext run(String... args) {
//構造一個任務執行觀察期
StopWatch stopWatch = new
StopWatch();
//開始執行,記錄開始時間
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//1
configureHeadlessProperty();
//2
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//3
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//4
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//5
configureIgnoreBeanInfo(environment);
//6
Banner printedBanner = printBanner(environment);
//7
context = createApplicationContext();
//8
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//9
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//10
refreshContext(context);
//2.0版本中是空實現
//11
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//12
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;
}
複製代碼
SpringApplication的run方法主要分爲如下幾步:
configureHeadlessProperty()
設置 headless 模式,即設置系統屬性java.awt.headless,它是J2SE的一種模式,用於在缺乏顯示屏,鍵盤,或者鼠標時的系統配置,該屬性會被設置爲true,更多的信息能夠參考這裏
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)));
}
複製代碼
SpringApplicationRunListeners listeners = getRunListeners(args);
複製代碼
getRunListeners(args)
也是經過 SpringFactoriesLoader
從META-INF/spring.factories
查找到並加載的SpringApplicationRunListener
。該類其實是監聽SpringApplication的run方法的執行
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) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//經過SpringFactoriesLoader能夠查找到並加載的SpringApplicationRunListner
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
這裏的SpringApplicationRunListener監聽器與SpringApplication時加載的ApplicationListener監聽器不一樣,SpringApplicationRunListener是SpringBoot新增的類,SpringApplicationRunListener目前只有一個實現類EventPublishingRunListener。雖說是新增的, 可是它們之間是有聯繫的,它們之間的的關係是經過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯繫起來的
更詳細的分析請參閱 :SpringBoot源碼分析之SpringBoot的啓動過程
將args參數封裝成 ApplicationArguments
對象
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
複製代碼
官網對 ApplicationArguments
的解釋以下
根據listeners
和applicationArguments
建立並配置當前SpringBoot應用將要使用的Enviroment
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
複製代碼
遍歷調用全部SpringApplicationRunListener的enviromentPrepared()
方法就是宣告當前SpringBoot應用使用的Enviroment準備好了
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
Boolean.class, Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
複製代碼
打印banner標誌,就是啓動SpringBoot項目時出現的Spring
字樣,固然咱們也能夠自定義banner,這裏就很少說了
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
複製代碼
createApplicationContext()
根據用戶是否明確設置了applicationContextClass類型以及SpringApplication初始化階段的推斷結果,決定該爲當前SpringBoot應用建立什麼類型的ApplicationContext並建立完成。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
protected ConfigurableApplicationContext createApplicationContext() {
//用戶是否明確設置了applicationContextClass,在SpringApplication中有對應的setter方法
Class<?> contextClass = this.applicationContextClass;
//若是沒有主動設置
if (contextClass == null) {
try {
//判斷當前應用的類型,也就是以前SpringApplication初始化階段的推斷結果
switch (this.webApplicationType) {
//servlet應用程序
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
//reactive響應式程序
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
//默認類型
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製代碼
在SpringBoot官網對ApplicationContext的類型是以下定義的:
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
複製代碼
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
這裏也是經過SpringFactoriesLoader加載META-INF/spring.factories中key爲SpringBootExceptionReporter的全類名的value值
SpringBootExceptionReporter
是一個回調接口,用於支持對SpringApplication
啓動錯誤的自定義報告。裏面就一個報告啓動失敗的方法org.springframework.boot.diagnostics.FailureAnalyzers
FailureAnalyzer
和FailureAnalysisReporter
實例private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//設置應用的環境
context.setEnvironment(environment);
//對 context 進行了預設置
postProcessApplicationContext(context);
applyInitializers(context);
遍歷調用SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot應用使用的ApplicationContext準備好了
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//遍歷調用SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext裝填完畢
listeners.contextLoaded(context);
}
複製代碼
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
複製代碼
遍歷調用這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
複製代碼
設置資源加載器,加載各類beans到ApplicationContext對象中
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
複製代碼
進入內部的refresh()方法,準備環境所需的bean工廠,經過工廠產生環境所需的bean,重點就是產生bean
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
複製代碼
上下文刷新後調用該方法,目前沒有操做
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
複製代碼
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(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);
}
}
}
複製代碼
查找當前的ApplicationContext中是否註冊有CommandLineRunner或者ApplicationRunner,若是有,就遍歷執行他們。
上面從SpringApplication的初始化到SpringApplication.run()方法執行,基本上按照其內部函數調用的順序一步一步分析下來,內容很是多,很容易把人搞暈。在網上發現一張圖,圖出自SpringBoot啓動流程解析,畫的比較清楚明白,把SpringBoot啓動整個流程都包含進來了
上面的分析都是基於SpringBoot2.0版本,在以前的版本,內容上可能有些誤差,大致思路是差很少的。在閱讀源碼的過程當中,看了不少前人分析的博客文章,也借鑑了他們的分析流程,有點「前人栽樹,後人乘涼」的感受,如今「取之網絡,再回饋之網絡」