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中的全名
类型编码
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
class或interfaceLclassName
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来读取文件中的值

image.png
类似于这样,键为传入的接口的全限定名,值为其实现类的全限定名,多个实现类通过,分隔

  • 然后存入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方法所在类