Spring Boot的构造流程

在Spring Boot的应用中,一般都是直接使用SpringApplication的静态方法run来运行,当然也可以先创建SpringApplication实例并设置一些属性,然后调用其对象方法run来运行。实际上在静态的run方法中也是通过创建一个SpringApplication实例然后调用实例方法run。所以两种方法本质上没有区别。 本文分析Spring Boot的构造原理,也就是创建SpringApplication实例的过程。


本文分析的Spring Boot源码版本是2.7.x,Spring Framework源码版本是5.3.22

SpringApplication构造方法

SpringApplication中有两个构造方法,其中一个没有设置资源加载器(ResourceLoader)。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details). The instance can be customized before calling
 * {@link #run(String...)}.
 * @param primarySources the primary bean sources
 * @see #run(Class, String[])
 * @see #SpringApplication(ResourceLoader, Class...)
 * @see #setSources(Set)
 */
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}
/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details). The instance can be customized before calling
 * {@link #run(String...)}.
 * @param resourceLoader the resource loader to use
 * @param primarySources the primary bean sources
 * 这里primarySources并没有强制要求一定是主类,一般其他直接或间接标注@EnableAutoConfiguration的类都可以
 * @see #run(Class, String[])
 * @see #setSources(Set)
 */
@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));

    // 推断web应用类型,共有三种
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载启动注册初始化器,默认在SpringBoot中是没有提供的,但在Spring Cloud中会使用。
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

    // 加载并初始化ApplicationContextInitializer及相关实现类(目前的版本共有5个),然后赋值给属性initializers
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 同理,加载ApplicationListener及相关实现类
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 推断main方法所在的类
    this.mainApplicationClass = deduceMainApplicationClass();
}

重点关注第二个构造器的实现,可以发现其内部主要做了下面几件事情:

  • 属性赋值:这没有什么好说的,就是把资源加载器(resourceLoader)和主配置源(primarySources)这两个参数赋值给当前类中的属性。
  • 推断应用类型;
  • 加载一些预配置的类并创建其实例:
    • 加载并创建启动注册初始化器(BootstrapRegistryInitializer)对象,默认情况下在Spring Boot中是没有在spring.factories文件中配置该类型的。
    • 加载并创建应用上下文初始化器(ApplicationContextInitializer)对象,会加载下面这些类型:
    v2.7.x
    properties
    spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    • 加载并创建应用监听器(ApplicationListener)对象,会加载下面这些类型:
    v2.7.x
    properties
    spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.context.logging.LoggingApplicationListener,\
    org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
  • 推断主方法(main)所在类;

其中几个加载类的操作都是调用的同一个方法,所以一起分析。

推断应用类型

Spring Boot支持两种类型的web应用的自动配置:Servlet和Reactive。在构造过程中就判断是哪种类型,是为后续运行过程做准备。运行过程中会创建ApplicationContextEnvironment对象,都会判断当前的应用是哪种类型。 当然Spring Boot也支持非web类型,不过一般用得少。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java
/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 */
public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 * 非Web应用
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 * 基于Servlet的Web应用
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 * 基于Reactive的Web应用
	 */
	REACTIVE;

	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";

	// 基于classpath下面是否存在某些类来推断WEB类型
	static WebApplicationType deduceFromClasspath() {
		// DispatcherServlet和ServletContainer不存在,且存在DispatcherHandler则为REACTIVE类型
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}

		// Servlet和ConfigurableWebApplicationContext任意一个不存在,则返回NONE
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET; // 默认返回SERVLET
	}

}

可以发现,Spring Boot是通过判断类路径下是否有相应的类来判断是哪种类型,这种方法在Spring的框架中经常被使用。

getSpringFactoriesInstances方法

SpringApplication类中,该方法有两种重载实现。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
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
    // 加载预配置在spring.factories中的配置项
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

    // 创建实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

    // 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

loadFactoryNames方法

SpringFactoriesLoader类是Spring Framework提供的。

该方法的作用是获取预配置在spring.factories中的目标配置。

loadFactoryNames
loadSpringFactories
<
>
java
spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 获取类名称
    String factoryTypeName = factoryType.getName();
    // 从所有spring.factories文件中的配置项获取目标配置,这里的factoryTypeName是key,从map中获取value。
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
java
spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 从缓存中获取数据
    Map<String, List<String>> result = cache.get(classLoader);
    // 缓存命中则直接返回
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        // 获取所有META-INF/spring.factories文件所在的路径
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        // 遍历每一个spring.factories文件
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 加载spring.factories文件
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            // 遍历spring.factories文件中的每一项
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 获取key
                String factoryTypeName = ((String) entry.getKey()).trim();
                // 处理逗号分割的数组
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                // 将每一项配置添加到列表中
                for (String factoryImplementationName : factoryImplementationNames) {
                    // 如果key不存在则创建一个新的列表(第二个参数)
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            // 添加到列表中
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        // 对结果中的每个列表进行去重并转化为不可修改列表
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        // 将结果保存到缓存中
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

从Spring Boot 2.7开始,官方不再推荐使用/META-INF/spring.factories文件的方式进行自动配置。从Spring Boot 3.x开始,会移除掉相关支持。 而新的方式则是使用/META-INF/spring/<key>.imports文件,这里的key就是原先在spring.factories文件中的key,而新文件中的内容则是原先的值。

createSpringFactoriesInstances方法

该方法的作用是创建loadFactoryNames方法加载到的那些预配置好的类的实例。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
@SuppressWarnings("unchecked")
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
            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;
}

接下来就是对这些创建好的对象排序,该过程较简单,省略分析过程。

推断main方法所在类

这里Spring Boot的实现方式比较巧妙,通过创建一个异常并获取其栈桢列表。然后遍历该列表,从中会获取main方法所在的类。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
private Class<?> deduceMainApplicationClass() {
    try {
        // 通过异常的栈帧来判断入口类
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) { // 如果方法名为main则说明是入口类
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

通过这个例子也可以看出,一般对于开发者而言被动处理的异常(如惹人烦的NullPointerException),却可以主动使用它来实现一些功能。

总结

本文总体来说比较简单,主要分析了SpringApplication的构造方法的过程,一些资料将其称为Spring Boot的构造过程或原理,所以本文也以此作为标题。 Spring Boot的构造原理是在为后续的运行过程做准备,更多细节请参考Spring Boot的运行过程一文。