本文從源碼的角度上分析了Spring Boot的啟動流程。內容僅供參考使用,有不足之處請及時指出,也歡迎大家交流探討。
Spring Boot 啟動類
- Spring boot啟動類相關代碼
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
- Spring boot啟動類分析
Spring boot啟動主要分為倆部分:@SpringBootApplication和SpringApplication的run方法。
@SpringBootApplication
- @SpringBootApplication 相關源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 省略
}
- @SpringBootApplication 分析
@SpringBootApplication包含了@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan。
@SpringBootConfiguration包含了@Configuration,@Configuration標註的類可以被註冊到ApplicationContext中,@Configuration主要用來指定特定的配置類在不能使用SpringRunner的測試中。
@EnableAutoConfiguration根據配置的jar依賴自動配置spring application。
@ComponentScan自動獲取所有的spring組件,包含@Configuration聲明的類。
SpringApplication的run方法
- run方法相關源碼
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);
}
- run方法分析
SpringApplication的run方法本質上分為倆步。
- 新建一個SpringApplication實例
新建SpringApplication實例相關源碼
public SpringApplication(Class>... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
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.1 判斷webApplication類型
this.webApplicationType = deduceWebApplicationType();
// 1.2 設置構造器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 1.3 設置監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 1.4 推斷應用主類
this.mainApplicationClass = deduceMainApplicationClass();
}
1.1 判斷webApplication類型
判斷webApplication類型相關源碼
private WebApplicationType deduceWebApplicationType() {
// ClassUtils.isPresent 判斷用戶提供的類是否存在及被加載
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
public enum WebApplicationType {
/**
* 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
}
1.2 設置構造器
設置構造器相關源碼
public void setInitializers(
Collection extends ApplicationContextInitializer>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
private
return getSpringFactoriesInstances(type, new Class>[] {});
}
private
Class>[] parameterTypes, Object... args) {
// 獲取類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set
// 通過參數中的類加載器,從META-INF/spring.factories這個路徑下,獲得參數中指定類型的工廠實現類全路徑。
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根據names中的類的全路徑,對應實例化類
List
classLoader, args, names);
// 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
private
Class>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set
List
for (String name : names) {
try {
Class> instanceClass = ClassUtils.forName(name, classLoader);
// 判斷instanceClass是否按類型匹配type
Assert.isAssignable(type, instanceClass);
// 根據parameterTypes中指定的參數類型及參數順序獲取與傳入對象的構造函數相對應的構造器
Constructor> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
// 根據給定的構造器去實例化類
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
1.3 設置監聽器
設置監聽器相關源碼
public void setListeners(Collection extends ApplicationListener>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}
// private <T> Collection<T > getSpringFactoriesInstances(Class<T> type)方法前文已給出
1.4 推斷應用主類
推斷應用主類相關源碼
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方法
- SpringApplication實例run方法源碼
public ConfigurableApplicationContext run(String... args) {
// 2.1 計時工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection
// 2.2 設置無圖形界面
configureHeadlessProperty();
// 2.3 獲取spring運行監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 2.4 環境準備及配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 2.5 spring boot項目啟動打印
Banner printedBanner = printBanner(environment);
// 2.6 獲取spring context
context = createApplicationContext();
// 2.7 獲取異常報告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 2.8 spring context處理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(
context);afterRefresh(context, applicationArguments);
// 2.9 停止計時器
stopWatch.stop();
// 2.10 應用啟動日誌打印
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 2.11 調用Runners
// Runners是ApplicationRunner CommandLineRunner的實現類
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;
}
2.2 設置無圖形界面
設置無圖形界面相關源碼
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
2.3 獲取spring運行監聽器
獲取spring運行監聽器相關源碼
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
// private
2.4 環境準備及配置
環境準備及配置相關源碼
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;
}
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());
}
}
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
2.5 spring boot項目啟動打印
spring boot項目啟動打印相關源碼
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);}
2.6 獲取spring context
獲取spring context相關源碼
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_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);
}
/**
* The class name of application context that will be used by default for web
* environments.
*/
public static final String DEFAULT_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";
/**
* 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";
2.7 獲取異常報告器
獲取異常報告器相關源碼前文已給出。
2.8 spring context處理
spring context處理相關源碼
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 設置環境
context.setEnvironment(environment);
// ApplicationContext後置處理
postProcessApplicationContext(context);
// 在context刷新之前應用構造器
applyInitializers(context);
// 調用監聽器的contextPrepared方法
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
Assert.notEmpty(sources, "Sources must not be empty");
// Application context加載beans
load(context , sources.toArray(new Object[0]));
// 調用監聽器的contextLoaded方法
listeners.contextLoaded(context);
}
private void refreshContext(ConfigurableApplicationContext context) {
// 刷新底層的application context
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
// Context刷新後調用
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
2.11 調用Runners
調用Runners相關源碼
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
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);
}
獲取Java架構進階資料,關注私信回覆暗號555,獲取
閱讀更多 以JAVA架構贏天下 的文章