SpringBoot启动流程分析(一)- SpringApplication的构造过程分析
在编写SpringBoot程序时,通常我们都会这样写
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
- @SpringBootApplication:这个注解就标识这该类是SpringBoot项目的启动类,它会为我们做一些自动配置,组件扫描的工作
- SpringApplication.run(App.class, args):该方法的调用就是用于启动该SpringBoot项目
今天我们来分析一下SpringApplication这个类,看看它的内部实现,SpringBoot到底为我们做了什么,大致内容如下
- SpringApplication的构造方法调用
- SpringApplication构造方法中的执行流程分析
SpringApplication的构造方法
首先进入**org.springframework.boot.SpringApplication#run(Class<?>, String...)**方法
/**
* 一个静态的工具方法,用于根据指定的source和args来启动SpringApplication
* @param primarySource 主要的需要加载的源
* @param args 命令行参数
* @return 返回一个ConfigurableApplicationContext
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
- 将传入的source转换为Class数组
ConfigurableApplicationContext类的介绍
接着会调用org.springframework.boot.SpringApplication#run(jClass<?>[], String[])方法
/**
* 一个静态的工具方法,用于根据指定的source和args来启动SpringApplication
* @param primarySources 主要的需要加载的源数组
* @param args 命令行参数
* @return 返回一个ConfigurableApplicationContext
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
- 该方法首先构造一个SpringApplication实例
- 然后调用run方法
- 并返回一个ConfigurableApplicationContext
这里我们先分析第一点,调用run方法的分析会放到后面的文章
org.springframework.boot.SpringApplication#SpringApplication(jClass<?>...)
/**
* @param primarySources 接收一个Class数组作为源
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
- 构造方法内部会调用另一个构造方法
org.springframework.boot.SpringApplication#SpringApplication(ResourceLoader, Class<?>...)
org.springframework.boot.SpringApplication#SpringApplication(ResourceLoader, Class<?>...)
/**
* 用于创建SpringApplication实例,ApplicationContext会从给定的primarySources中加载bean
* @param resourceLoader 加载资源所需要用到的resourceLoader
* @param primarySources bean的主要来源
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// 保证primarySources不能为null
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
this.resourceLoader = resourceLoader;
这一步主要是给SpringApplication中的resourceLoader成员赋值,这里为null
ResourceLoader类的介绍
this.primarySources = new LinkedHashSet_<>(_Arrays.asList(primarySources));
this.primarySources是一个Class<?>类型的集合
private Set<Class<?>> primarySources;
将传入的sources数组转换为LinkedHashSet,可以达到去重的作用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.webApplicationType是一个WebApplicationType类型的实例
private WebApplicationType webApplicationType;
WebApplicationType
该类是一个枚举类,定义了WebApplication的三种类型,分别是NONE,SERVLET,REACTIVE
public enum WebApplicationType {
/**
* 该应用不是一个Web应用,启动时不应该启动一个嵌入式的web服务器
*/
NONE,
/**
* 这个应用是标准的基于Servlet的Web应用
*/
SERVLET,
/**
* 这个应用是一个基于Reactive的Web应用
*/
REACTIVE;
/** 下面定义了一系列的Class常量,该类的方法中会用到 */
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
}
org.springframework.boot.WebApplicationType#deduceFromClasspath
该方法用于根据classpath中的类来推断出当前应用的类型
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
- 首先判断classpath中是否存在WebFlux相关的类,如果存在,则该应用为Reactive Web类型
- 如果不存在,则接着判断是否存在servlet相关的类,如果不存在,则该应用为普通应用
- 如果都不存在,则该应用为ServletWeb类型
org.springframework.util.ClassUtils#isPresent
该方法用于判断一个类是否存在或者是否能够被加载
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
}
catch (IllegalAccessError err) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.getMessage(), err);
}
catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
return false;
}
}
- 先调用ClassUtils#forName方法查找类是否存在,如果存在则返回true
- 如果不存在会抛出异常,那么就返回false
org.springframework.util.ClassUtils#forName
该方法用于代替Class#forName方法,与Class#forName不同的是,该方法可以返回原生类型或数组类型
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
// 判断是否为原生类型
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
// 从缓存中查找当前传入类名所属的类是否存在
clazz = commonClassCache.get(name);
}
if (clazz != null) {
// 原生类型直接返回
return clazz;
}
// 如果都没有查找到,则获取classLoader
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
return Class.forName(name, false, clToUse);
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return Class.forName(innerClassName, false, clToUse);
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
- 首先判断传入的类型是否为原生类型,如果不是,尝试从缓存中获取,如果是原生类型,则直接返回原生类型
- 接着判断传入的类名是否符合下面3种情况,如果符合其中任意一种,就返回其类型
- java.lang.String[]
- [Ljava.lang.String;
- [[I or 或者[[Ljava.lang.String;
- 如果都没有查找到,则尝试获取classLoader,然后通过获取到的classLoader来获取该类的类类型
- 如果还是没有,则抛出ClassNotFoundException
org.springframework.util.ClassUtils#getDefaultClassLoader
该方法用于获取默认的ClassLoader
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
- 首先获取当前线程的contextClassLoader
- 如果classloader == null,表示没有获取到contextClassLoader,则获取加载ClassUtils这个类的classLoader
- 如果classloader还是null,表示该类是由bootstrapClassLoader加载的,则返回当前SystemClassLoader
setInitializers_((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))_;
该方法用于设置初始化器
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
// 简单的赋值操作
this.initializers = new ArrayList<>(initializers);
}
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(Class)
该方法用于获取SpringFactoriesInstances
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<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
首先调用org.springframework.boot.SpringApplication#getClassLoader方法来获取classLoader
public ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
- 首先判断resourceLoader是否为null,如果是第一次启动,这里一般都是为null
- 如果不为空,则返回resourceLoader的classLoader
- 如果为空,则通过Classutils#getDefaultClassLoader方法来获取classLoader
接着调用org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames方法
该方法用于从META-INF/spring.factories
中加载给定类型的实现类的类名,这里的给定类型为ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 返回loadSpringFactories的结果,如果没有获取到,就返回一个空的集合
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 先通过class#getName获取给定类类型的名字,该方法返回类在JVM中的全名
类型 | 编码 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
class或interface | LclassName |
array | [,数组有几维,左中括号就有几个 |
比如,new Object[][3].getClass().getName()的结果为**[[Ljava.lang.Object; (分号不能少)**
**
然后调用私有静态方法org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
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()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
- 首先去缓存中根据classLoader获取缓存,如果缓存存在,则直接返回
- 如果缓存不存在,则从META-INF/spring.factories文件中获取,获取之后放入缓存
先看一下这个所谓的缓存是什么
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
它其实是SpringFactoriesLoader的一个私有的常量,类型为ConcurrentReferenceHashMap,每次我们从spring.factories中读取到数据后,就会放入缓存,下次再读的话就会从缓存中获取了
接着看一下如何从spring.factories中获取数据
- 首先判断classLoader是否为空,如果不为空,则调用classLoader.getResources("META-INF/spring.factories")获取,如果为空,则调用classLoader.getSystemResources("META-INF/spring.factories")来获取
- 然后实例化MultiValueMap,这是一个多值Map,即一个key对应的value中有多个值
- 然后通过UrlResource来读取文件中的值
类似于这样,键为传入的接口的全限定名,值为其实现类的全限定名,多个实现类通过,
分隔
- 然后存入multiValueMap中,key为接口全限定名,值为实现类的全限定名,再放入缓存,并返回该map
从spring.factories文件中获取到结果后,调用org.springframework.boot.SpringApplication#createSpringFactoriesInstances方法来创建实例
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
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;
}
这里就是通过反射来创建接口ApplicationContextInitializer实现类的实例
最后对这些实例进行排序,决定这些Bean实例化、注入的顺序
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
设置监听器,该方法与上一个方法的实现逻辑完全相同,只不过这次找的是ApplicationListener的实现类
this.mainApplicationClass = deduceMainApplicationClass_()_;
该方法主要是找到main方法所在类,然后赋值给成员变量mainApplicationClass
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;
}
这里比较有意思的是它生成了一个RuntimeException,然后获取其堆栈信息,再遍历这些堆栈信息获取到方法名为main的所在的类
总结
最后,总结一下整个SpringApplication的构造步骤
- 首先调用一个参数的构造方法,传入为调用run方法的类的类名作为bean的来源
- 然后调用两个参数的构造方法,将bean来源类型改造为数组,并传入resourceLoader用于加载资源
- 根据classpath推断当前应用的类型(NONE,SERVLET,REACTIVE)
- 通过查找spring.factories来设置初始化器和监听器
- 最后找到main方法所在类