SpringBoot 启动流程(一):SpringApplication类的构造函数

一、引言

SpringBoot工程,让工程应用开发更加方便,屏蔽了工程初始化的细节过程。理解SpringBoot项目的初始化过程,能够让读者更加深入的理解Spring框架加载的原理,实现在SpringBoot工程初始化的过程中,进行一些自定义的操作。接下来将基于SpringBoot、SpringFramwork来进行分析和学习。使用的版本:

项目版本下载地址
SpringBoot2.0.4releasehttps://github.com/spring-projects/spring-boot
SpringFramework5.0.8.releasehttps://github.com/spring-projects/spring-framework

二、SpringApplication初始化过程

SpringBoot项目,我们直接调用SpringApplication类的run方法,就能完成项目的启动过程:

@SpringBootApplication
public class MySpringBootApplication {
	public static void main(String[] args) {
		SpringApplication.run(MySpringBootApplication.class, args);		
	}
}

SpringApplication类的静态方法run方法,会接收当前运行的class对象,以及启动参数(注意,该参数是从MySpringBootApplication的main方法中获得的,比如xms参数,xmx参数,profile=prod参数等等)。

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 *
	 * @param primarySource the primary source to load
	 * @param args          the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?> primarySource,
													 String... args) {
		return run(new Class<?>[]{primarySource}, args);
	}

该静态方法会先初始化一个SpringApplication实例,完成构造函数的操作。然后,调用SpringApplication.run()方法,完成SpringApplication的整个初始化流程。本篇,先讲解SpringApplication的构造函数所做的工作,构造方法中进行了WebApplicationType的推断、ApplicationContextInitializer、ApplicationListener的实例初始化工作。以下,是SpringApplication构造函数的代码

@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));
		//推断应用类型,后面会根据类型初始化对应的环境。常用的一般都是servlet环境
		this.webApplicationType = deduceWebApplicationType();
		//初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
		//spring组件spring-context组件中的一个接口,用于ConfigurableApplicationContext通过调用refresh函数来初始化Spring容器之前的回调函数
		//主要是spring ioc容器刷新之前的一个回调接口,用于处理自定义逻辑。
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//根据调用栈,推断出 main 方法的类名
		this.mainApplicationClass = deduceMainApplicationClass();
	}

1、推断应用类型WebApplicationType

WebApplicationType有三种类型NONE(非Web应用),SERVLET(SpringMVC),REACTIVE(如Spring WebFlux)。我们一般采用的是SERVLET。

package org.springframework.boot;

/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 * 枚举可在配置文件中配置 例如: spring.main.web-application-type=servlet
 */
public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 * 应用程序不是web应用,也不应该用web服务器去启动
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 * 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web(tomcat)服务器。
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 * 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服务器。
	 */
	REACTIVE

}

推断过程,主要是结合应用加载全路径类名来推断,从当前源码中,能否通过ClassLoader成功加载到对应的类,判断对应的类型。

/**
	 * The class name of application context that will be used by default for reactive web
	 * environments.
	 */
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	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";
 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 = (Class)commonClassCache.get(name);
        }

        if (clazz != null) {
            return clazz;
        } else {
            Class elementClass;
            String elementName;
            if (name.endsWith("[]")) {
                elementName = name.substring(0, name.length() - "[]".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[L") && name.endsWith(";")) {
                elementName = name.substring("[L".length(), name.length() - 1);
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[")) {
                elementName = name.substring("[".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else {
                ClassLoader clToUse = classLoader;
                if (classLoader == null) {
                    clToUse = getDefaultClassLoader();
                }

                try {
                	//主要是这里,classloader如果能够从当前源码中,加载到对应的类,则为对应的类型
                    return clToUse != null ? clToUse.loadClass(name) : Class.forName(name);
                } catch (ClassNotFoundException var9) {
                    int lastDotIndex = name.lastIndexOf(46);
                    if (lastDotIndex != -1) {
                        String innerClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);

                        try {
                            return clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName);
                        } catch (ClassNotFoundException var8) {
                            ;
                        }
                    }

                    throw var9;
                }
            }
        }
    }

2、加载应用上下文初始化器ApplicationContextInitializer

ApplicationContextInitializer是一个回调接口,用于在ConfigurableApplicationContext刷新应用上下文前,进行一些启动事项设置。

org.springframework.context.ApplicationContextInitializer
Callback interface for initializing a Spring ConfigurableApplicationContext prior to being refreshed.
Typically used within web applications that require some programmatic initialization of the application context. For example, registering property sources or activating profiles against the context’s environment. See ContextLoader and FrameworkServlet support for declaring a “contextInitializerClasses” context-param and init-param, respectively.
ApplicationContextInitializer processors are encouraged to detect whether Spring’s Ordered interface has been implemented or if the @Order annotation is present and to sort instances accordingly if so prior to invocation.

以下是构造函数中的代码,主要作用是设置initializer,springboot项目在spring-boot、spring-boot-autoconfigure两个工程中,默认有6个initializer。

//主要是spring ioc容器刷新之前的一个回调接口,用于处理自定义逻辑。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

initializer默认从spring.factories加载。以下是默认的initializer的位置:
在这里插入图片描述
在这里插入图片描述
跟踪代码,我们可以看到springboot项目默认的6个initializer:
在这里插入图片描述

3、加载应用监听器ApplicationListener

ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的。ApplicationListener的默认Listener是从spring-boot、spring-boot-autoconfigure两个工程中的spring.factories文件中,加载listener。 ApplicationListener 是Spring的事件监听器,典型的观察者模式,通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现对spring容器全生命周期的监听,当然也可以自定义监听事件。

在这里插入图片描述

三、总结

关于 SpringApplication 类的构造过程,到这里我们就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在spring容器创建之前做一些预备工作,和定制化的需求。比如,自定义SpringBoot的Banner,比如自定义事件监听器,再比如在容器refresh之前通过自定义 ApplicationContextInitializer 修改配置一些配置。

参考资料:
SpringBoot启动流程分析:https://www.cnblogs.com/hello-shf/p/10976646.html
Spring IOC 容器源码分析:https://javadoop.com/post/spring-ioc#toc_3
Spring官网介绍:https://spring.io/projects/spring-boot#learn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值