SpringBoot启动流程浅分析

661 阅读3分钟

SpringBoot启动流程浅分析

一.启动类

1.@SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

image-20220327122913567

关于自动配置的流程,可以去看这篇博文

【Spring Boot】Spring Boot 自动配置原理 图文并茂_秋日的晚霞的博客-CSDN博客

2.main方法

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

跟进去

image-20220327123111967

首先看方法声明 静态方法 返回一个 ConfigurableApplicationContext 接口实现类对象

image-20220327123422047

image-20220327123612064

可以看到有非常多的实现类及子接口 那到底是怎么选择实现类对象的? 带着这个问题继续往下跟源码

image-20220327123733436

primarySources   //主启动类的class对象  
args   //main方法的启动参数

在这new 了一个 SpringApplication 实例,调用了run方法

new SpringApplication()

跟进 new SpringApplication(primarySources) 方法

public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

创建一个新的 SpringApplication 实例。应用程序上下文将从指定的主要来源加载 bean

继续跟进构造方法

image-20220327124335545

resourceLoader  //资源加载器   此时为null 

this.resourceLoader = resourceLoader;
primarySources  //容器中bean的来源   此时为主启动类的字节码对象

这里断言 primarySources 不能为null 否则将抛出异常

Assert.notNull(primarySources, "PrimarySources must not be null");

方法形参上 primarySources 是一个可变参 接下来将primarySources 变成了一个 LinkedHashSet

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

LinkedHashSet set集合 元素不重复 底层是链表 保证了存储有序

然后执行了 这么一行代码

this.webApplicationType = WebApplicationType.deduceFromClasspath();

这个 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 服务器
	 */
	SERVLET,

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

image-20220327125229813

判断当前项目类型 这里返回 WebApplicationType.SERVLET 也就是当前 SpringApplication 的类型为 Web 应用程序,内置servlet Web 服务器

接下来设置了监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

然后设置程序入口

this.mainApplicationClass = deduceMainApplicationClass()

Spring容器启动类对象实例化

SpringApplication.run()

接下来看run方法做了什么

image-20220327125749885

StopWatch stopWatch = new StopWatch();   //计时器
stopWatch.start();  //开始计时

设置了一个名为java.awt.headless的系统属性

configureHeadlessProperty()

获取Spring应用上下文引导类的监听器,

SpringApplicationRunListeners listeners = getRunListeners(args)

跟进去发现,发布了ApplicationStartingEvent事件

listeners.starting();

image-20220327131941245

封装参数SpringApplication引导类需要的参数

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args)

准备环境

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

根据当前环境忽略不需要的bean

configureIgnoreBeanInfo(environment);

image-20220327132203736

打印 Banner

Banner printedBanner = printBanner(environment)

创建ApplicationContext上下文 之前看到 webApplicationType 的类型是 SERVLET

context = createApplicationContext()

image-20220327133346608

image-20220327133705760返回AnnotationConfigServletWebServerApplicationContext字节码对象 然后调用 BeanUtils.instantiateClass 实例化

class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
		implements AnnotationConfigRegistry 

继承自 ServletWebServerApplicationContext

刷新上下文 最终复用了Spring的刷新容器方法

refreshContext(context);

调用 ConfigurableApplicationContext .refresh()方法

再调用org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh

最后调用 org.springframework.context.support.AbstractApplicationContext#refresh

刷新容器的12个方法可以去看这篇博文

【Spring】SpringIOC容器启动过程源码分析 以及 循环依赖问题_秋日的晚霞的博客-CSDN博客

image-20220327132839716

image-20220327132925712

最后就是停止计时以及打印日志

        stopWatch.stop();

        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }