1. 程式人生 > 程式設計 >SpringBoot啟動流程原始碼分析(1)

SpringBoot啟動流程原始碼分析(1)

SpringApplication例項化

springboot簡化spring的大量xml的配置檔案,為了實現統一化配置管理,一般引數配置 .properties檔案或者.yml檔案,統一採用main方法來進行啟動,內部集成了tomcat,實現安全監控,並且支援熱部署。

`

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}
複製程式碼

調run方法之後,我們其實發現先去建立SpringApplication的例項。

public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
    return run(new Class[]{primarySource},args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}
複製程式碼

`

接下來我們來繼續看構建SpringApplication例項的時候,做了什麼具體細節。

`

public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources,"PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	//推斷應用型別,後面會根據型別初始化對應的環境。常用的一般都是servlet環境
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	////初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//初始化classpath下所有已配置的 ApplicationListener
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//根據呼叫棧,推斷出main方法的類名
	this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼

`

獲取ApplicationContextInitializer例項的物件,會呼叫initialize方法。

`

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes,Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type,classLoader));
	//建立springFactories例項
	List<T> instances = createSpringFactoriesInstances(type,parameterTypes,classLoader,args,names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
複製程式碼

`

獲取ApplicationListener例項的物件,其中onApplicationEvent可以監聽全部事件。

`

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,classLoader));
	List<T> instances = createSpringFactoriesInstances(type,names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
複製程式碼

`

SpringApplication的run方法分析,啟動spring容器,載入bean。

`

public ConfigurableApplicationContext run(String... args) {
    //記錄程式執行時間
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	//ConfigurableApplicationContext Spring上下文
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	//從META-INF/spring.factories中獲取監聽器
	//1、獲取並啟動監聽器。
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//2、構建應用上下文環境
		ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
		configureIgnoreBeanInfo(environment);
		//列印banner
		Banner printedBanner = printBanner(environment);
		//3、初始化應用上下文
		context = createApplicationContext();
		//例項化SpringBootExceptionReporter.class,用來支援報告
		//關於啟動報錯
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class },context);
		//4、重新整理應用上下文前的準備階段
		prepareContext(context,environment,listeners,applicationArguments,printedBanner);
		//5、重新整理應用上下文
		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,null);
		throw new IllegalStateException(ex);
	}
	return context;
}
複製程式碼

`

我們這裡來說明一下SpringApplication的run方法做了什麼事情。
第一步:獲取並啟動監聽器
第二步:構造應用上下文環境
第三步:初始化應用上下文
第四步:重新整理應用上下文前的準備階段
第五步:重新整理應用上下文
第六步:重新整理應用上下文後的擴充套件介面

①、啟動監聽器SpringApplicationRunListeners

`

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,names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
複製程式碼

`

小夥伴有沒有發現跟上面的ApplicationListener載入業務邏輯一樣。EventPublishingRunListener開始監聽事件。

②、構造應用上下文環境

構建應用上下文環境是去載入springboot的*.properties或者*.yml檔案。 `

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
	// Create and configure the environment
	//建立並且配置相應的環境
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//根據使用者配置,配置environment系統環境
	configureEnvironment(environment,applicationArguments.getSourceArgs());
	//啟動相應的監聽器,其中一個重要的監聽器 //ConfigFileApplicationListener 就是載入專案配置檔案的監聽器。
	listeners.environmentPrepared(environment);
	//繫結springbootApplication環境
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
複製程式碼

`

③、初始化應用上下文

springboot應用上下文的環境,我們以SERVLET環境為例。

`

/**
 * The application should not run as a web application and should not start an
 * embedded web server.
 */
NONE,/**
 * The application should run as a servlet-based web application and should start an
 * embedded servlet web server.
 */
SERVLET,/**
 * The application should run as a reactive web application and should start an
 * embedded reactive web server.
 */
REACTIVE;
複製程式碼

`

下面是獲取context上下文例項。 `

/**
 * The class name of application context that will be used by default for non-web
 * environments.
 */
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
		+ "annotation.AnnotationConfigApplicationContext";

/**
 * The class name of application context that will be used by default for web
 * environments.
 */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
		+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

/**
 * The class name of application context that will be used by default for reactive web
 * environments.
 */
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
		+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

//獲取context例項
protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			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);
}
複製程式碼

` 對應三種型別,web應用構建上下文預設為AnnotationConfigServletWebServerApplicationContext。我們可以來看一下AnnotationConfigServletWebServerApplicationContext的例項圖。

此圖的繼承關係如下:
AnnotationConfigServletWebServerApplicationContext類繼承了ServletWebServerApplicationContext
ServletWebServerApplicationContext繼承了GenericWebApplicationContext
GenericWebApplicationContext繼承了GenericApplicationContext
GenericApplicationContext繼承了AbstractApplicationContext
AbstractApplicationContext繼承了DefaultResourceLoader

應用上下文可以理解成容器的高階表現形式,應用上下文確實在IOC容器基礎上豐富了一些高階功能。