加载中...

SpringBoot启动过程


前言

前几天看着源码与网上的博客自己也理解并写了以下关于SpringBoot的自动装配原理的文章,今天突然想到,既然都看了自动装配了,那SpringBoot的启动流程又是什么样的?平时写代码都是有一个main函数,但是SpringBoot只有一段简单的启动代码:

@SpringBootApplication
public class StaffingSystemApplication {

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

这个run的流程是什么呢?

还是从入口看起

SpringBoot启动就是从这个run方法进行的,我们先看看里面究竟有什么:

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类构造方法

可以看到,run方法进入到后一个方法后,先创建了一个SpringApplication类,SpringApplication最终调用的构造方法如下:

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();
    //设置bootstrapRegistryInitializers
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
    //设置Initializers,通过getSpringFactoriesInstances()得到需要设置的Initializer。
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //设置Listeners,通过getSpringFactoriesInstances()从/META-INF/spring.factories中读取所有的ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //找出main函数所在的类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

下面依次看一下其中几个函数:

public enum WebApplicationType{
	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE;
    
    //判断环境
    static WebApplicationType deduceFromClasspath() {
        //REACTIVE类型
		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)) {
                //非web环境类型
				return WebApplicationType.NONE;
			}
		}
        //普通SERVLET类型
		return WebApplicationType.SERVLET;
	}
}
//找到main所在的类
private Class<?> deduceMainApplicationClass() {
		try {
            //得到堆栈信息
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
                //遍历堆栈,如果找到main类,将其加载出来后退出
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

小结一下,这个new SpringApplication主要做了以下几件事:

1、判断当前的环境是REACTIVE、NONE还是SERVLET

2、设置bootstrapRegistryInitializers

3、通过getSpringFactoriesInstances()方法,从类路径下/META-INF/spring.factories文件中获取所有的ApplicationContextInitializer,加入容器

4、同样通过getSpringFactoriesInstances()方法,从类路径下/META-INF/spring.factories文件中获取所有的ApplicationListener,加入容器

5、从堆栈中找出main函数所在的类,Class.ForName加载并初始化到内存

调用SpringApplication类的run方法

public ConfigurableApplicationContext run(String... args) {
    //看起来像是计时用的
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    //上下文信息
		ConfigurableApplicationContext context = null;
    //似乎是设置系统属性,应该对理解没有影响
		configureHeadlessProperty();
    //读取类路径/META-INF/spring.factories中所有SpringApplicationRunListener
		SpringApplicationRunListeners listeners = getRunListeners(args);
    //开启监听
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
            //封装console传进来的参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //准备配置环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
            //打印那个springboot的banner图
			Banner printedBanner = printBanner(environment);
            //创建ApplicationContext上下文信息
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            //准备上下文环境,实例化bean对象
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //刷新上下文,创建Tomcat容器
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            //监听器执行started,表示启动成功
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

上述的关键方法在于refreshContext(context),目前看得有一点懵,头昏脑胀得,以后再来具体仔细的补补内部的逻辑

再小结以下,这个run方法从源码中可以看到主要做了以下几件事:

1、获取/META-INFO/spring.factories中获取所有SpringApplicationRunListeners
2、循环启动SpringApplicationRunListener,执行starting()方法
3、包装console传过来的参数。
4、准备环境,准备完成后,会调用Listener.environmentPrepared()方法
5、打印banner图
6、根据环境,创建Spring上下文ApplicationContext
7、预备上下文环境,在初始化应用后,先获取所有的Initializer,然后会调用所有Initializer.initializer()方法,再接着会调用Listener.contextPrepared()方法。在准备环境完成后,调用Listener.contextLoaded()方法,告知Context已经加载完毕。
8、刷新上下文,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat),初始化bean等操作
9、调用所有listeners.started()方法,表示启动成功
10、回调所有的ApplicationRunner和CommandLineRunner
11、返回Spring上下文SpringContext

总结

SpringBoot的run整个流程主要的部分大致就是上面的内容了,但目前有许多东西知识从名字推论和别人的博客知道的,源码和具体的意义可能还需要后面再看看

后记

后面发现一个很好的总结的流程图:SpringBoot启动结构图


文章作者: DestiNation
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DestiNation !
  目录