原創(chuàng)文章,轉(zhuǎn)載請標注出處:《Spring基礎(chǔ)系列-容器啟動流程(2)》
一、概述
上一篇文章講述了SSM架構(gòu)的web項目中容器的啟動流程,這一篇我們來看看SpringBoot項目中容器的啟動流程。
二、啟動流程
Springboot項目需要直接從main方法啟動。
源碼1-來自DemoApplicaiton(應(yīng)用自定義的)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
首先調(diào)用的是SpringApplication中的run方法。
源碼2-來自: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);
}
第一個run方法會調(diào)用第二個run方法。
在第二個run方法中有兩步:
- 第一步:創(chuàng)建SpringApplicaiton實例
- 第二步:調(diào)用run方法
首先我們先創(chuàng)建SpringApplication實例:
源碼3-來自:SpringApplication
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));
//判斷應(yīng)用類型
this.webApplicationType = deduceWebApplicationType();
//添加初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//添加監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//定位main方法所在類,并做記錄
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringBoot應(yīng)用有三種類型:
源碼4-來自:WebApplicationType
public enum WebApplicationType {
NONE,SERVLET,REACTIVE
}
解析:
- NONE:應(yīng)用不是web應(yīng)用,啟動時不必啟動內(nèi)置的服務(wù)器程序
- SERVLET:這是一個基于Servlet的web應(yīng)用,需要開啟一個內(nèi)置的Servlet容器(web服務(wù)器)
- REACTIVE:這是一個反應(yīng)式web應(yīng)用,需要開啟一個內(nèi)置的反應(yīng)式web服務(wù)器
三者的判斷條件如下
源碼5-來自:SpringApplication
private WebApplicationType deduceWebApplicationType() {
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;
}
上面的方法中涉及四個常量:
源碼6-來自:SpringApplication
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" };
至于代碼很是簡單。
然后是設(shè)置初始化器,結(jié)合之前的講述在SSM架構(gòu)的web應(yīng)用中也擁有初始化器,這兩個代表的是同一個概念,不同于之前的處理方式,這里僅僅是將初始化器找到并設(shè)置到initializers屬性中。
我們來看看它是如何獲取這些初始化器的:
源碼7-來自:SpringApplication、SpringFactoriesLoader
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
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));
//創(chuàng)建實例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
// 下面來自:SpringFactoriesLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//首先嘗試從緩存獲取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try
//從META-INF/spring.factories文件中獲取配置的內(nèi)容
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(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.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//添加到緩存?zhèn)溆? cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
由此可見,初始化器是從META-INF/spring.factories文件中獲取到的,那么我們就可以自建該目錄文件進行添加,前提是需要自定義初始化器。
還有這里只有在第一次調(diào)用時需要從文件讀取,第二次就可以從緩存獲取,哪怕需要的不是初始化器的內(nèi)容,因為第一次的時候就會將所有的配置內(nèi)容全部獲取并解析保存到緩存?zhèn)溆?。這也就為下一步設(shè)置監(jiān)聽器這一步省下了不少時間。
最后一步就是將main方法所在類做個記錄。
源碼8-來自:SpringApplication
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;
}
通過新建一個運行時異常的方式獲取方法調(diào)用棧,在棧中搜索方法名為main的方法所在的類。
如果我們想要獲取方法的調(diào)用棧也可以采用這種方式,使用異常來獲取調(diào)用棧。
完成SpringApplication實例的創(chuàng)建之后,直接調(diào)用其run方法,執(zhí)行應(yīng)用啟動。
源碼9-來自:SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置系統(tǒng)屬性headless,默認為true
configureHeadlessProperty();
//獲取所有的運行時監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//啟動監(jiān)聽器
try {
//封裝自定義參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根據(jù)參數(shù)配置環(huán)境environment
//如果是web項目則創(chuàng)建的是StandardServletEnvironment,否則是StandardEnvironment
//然后配置屬性和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//設(shè)置是否忽略BeanInfo,默認為true
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//創(chuàng)建ApplicationContext
//如果是Servlet web項目則創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext,如果是Reactive web
//項目則創(chuàng)建AnnotationConfigReactiveWebServerApplicationContext,否則AnnotationConfigApplicationContext
context = createApplicationContext();
//加載異常報告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//籌備上下文環(huán)境,也就是初始化
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
refreshContext(context);
//刷新后操作,這是兩個孔方法,是可以自定義實現(xiàn)邏輯的
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);//容器和應(yīng)用啟用之后,callRunner之前執(zhí)行
//容器啟動完成之后回調(diào)runner,包括: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;
}
這是整個容器啟動的所有邏輯入口,參照注釋即可理解,我們重點關(guān)注prepareContext方法,這個方法是在容器刷新之前的預(yù)初始化操作:
源碼10-來自:SpringApplication
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//設(shè)置environment到上下文
context.setEnvironment(environment);
//設(shè)置BeanName生成器
//設(shè)置資源加載器或者類加載器
postProcessApplicationContext(context);
//執(zhí)行初始化器
applyInitializers(context);
listeners.contextPrepared(context);//執(zhí)行監(jiān)聽器的contextPrepared操作
//配置日志信息
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//注冊單例(springApplicationArguments,springBootBanner)
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");
//加載Bean到容器
load(context, sources.toArray(new Object[0]));
//執(zhí)行監(jiān)聽器的contextLoaded操作
listeners.contextLoaded(context);
}
重點關(guān)注下load方法,他的目的是加載Bean定義:
源碼11-來自:SpringApplication
//為load操作做準備,創(chuàng)建資源讀取器掃描器
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
//創(chuàng)建BeanDefinition讀取器,掃描器(AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader,
//GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
//配置BeanName生成器,資源加載器,環(huán)境參數(shù)到讀取器和掃描器中
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
//執(zhí)行Bean的加載
loader.load();
}
//統(tǒng)計加載Bean的數(shù)量
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
//針對不同的情況加載bean資源
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//Resource資源可能是XML定義的Bean資源或者groovy定義的Bean資源
if (source instanceof Resource) {
return load((Resource) source);
}
//Packet資源一般是用于注解定義的Bean資源,需要掃描器掃描包
if (source instanceof Package) {
return load((Package) source);
}
//
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
針對不同的資源進行加載。
下面就要回到run方法中的重點操作refreshContext(context)了:
源碼12-來自:SpringApplication
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
這里也到了執(zhí)行refresh()方法的時候了,下面我們就要看看這個refresh了。