开始
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
这段代码相信大家一定不陌生了,接下来我们就以这段代码作为开端,来一步步的分析SpringBoot的启动流程。
SpringApplication:
/*
* 构建出一个新的SpringApplication并运行,创建并刷新一个新的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(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//重载的构造方法,resourceLoader为null
this.resourceLoader = resourceLoader;
//判断传入的primarySources是否为空
Assert.notNull(primarySources, "PrimarySources must not be null");
//将primarySources保存到LinkedList中,此时的primarySource是BootApplication.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//deduceFromClasspath()这个方法会判断当前类路径下是否有指定的类,从而决定应用程序的类型,类型分三种REACTIVE,NONE,SERVLET。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//getSpringFactoriesInstances()这个方法是实现SpringBoot自动配置的基础,是Spring自实现的SPI
//从META-INF/spring.factoies下加载ApplicationContextInitializer为key的全类名,并实例化
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//同上,加载ApplicationListener为key的全类名
setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class));
//获取运行程序的堆栈,看看运行的是哪个类的main方法,继而保存到上下文。
this.mainApplicationClass = deduceMainApplicationClass();
}
在SpringApplication的构造方法中,调用了两次getSpringFactoriesInstances(Class<?> type),根据传入的类型不同,去加载不同的资源,接下来我们就看一下这个方法是如何做的。
SpringApplication
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 = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 加载相关配置,set防止重复。
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, classLoader)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
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 {
//classLoader.getResources是从类路径下开始查找
//FACTORIES_RESOURCE_LOCATION= META-INF/spring.factories
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();
//使用当前的url实例化一个UrlResource
UrlResource resource = new UrlResource(url);
//加载资源到内存中
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//遍历当前资源的键值对
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//获取key
String factoryClassName = ((String) entry.getKey()).trim();
// StringUtils.commaDelimitedListToStringArray(str) 把传入的字符串按逗号分隔为数组
// 遍历获取key对应的value
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//追加到map中。
result.add(factoryClassName, factoryName.trim());
}
}
}
//加入缓存
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文件,并将其读入内存、解析,最后存入缓存中
那这么做有什么用呢?我们看一下spring.factories里到底有什么呢?
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
省略部分代码...
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
我们可以看到在SpringApplication的构造器中,分别获取了org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener这两个全类名所定义的全限定类名。
然后就是SpringBoot自动配置相关的知识了,在上一篇文章中也有所提及,接下来会重新写一篇着重研究一下SpringBoot的自动配置原理。
接下来我们看看在实例化SpringApplication之后,run方法中做了什么。
run
public class SpringApplication {
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
启动Spring上下文,创建并刷行一个新的ApplicationContext
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//启动时间记录
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//声明一个IOC容器对象,在try-catch中初始化,以便在出现异常的时候可以将IOC作为参数传入。
ConfigurableApplicationContext context = null;
//初始异常报告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//开启headless模式,设置系统数据java.awt.headless的值为true
configureHeadlessProperty();
//从spring.factories中获取SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
//调用所有监听器的starting方法,SpringBoot正在启动
listeners.starting();
try {
//将main方法中传入的args参数,包装成一个ApplicationArguments对象,方便获取
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备上下文的environment对象
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//配置忽略bean信息
configureIgnoreBeanInfo(environment);
//设置启动时的图标
Banner printedBanner = printBanner(environment);
//创建上下文
context = createApplicationContext();
//从spring.factories中获取key为SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新上下文
refreshContext(context);
//刷新完成之后调用,默认空实现,子类覆盖
afterRefresh(context, applicationArguments);
//启动结束
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//回调监听器的started方法
listeners.started(context);
// 调用所有ApplicationRunner和CommandLineRunner的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 回调监听器的 listening 方法,SpringBoot 启动完成!
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
}
至此,SpringBoot的启动流程我们已经大致过了一遍了, 但是还是有很多囫囵吞枣的地方,在接下来的文章中,我们也会逐一分析SpringBoot启动中涉及到的各种技术点。