从代码层面看spring boot启动过程

前言

我们都知道spring boot项目是通过main方法来启动运行的,但是main方法执行之后,spring boot都替我们完成了哪些操作,最终让我们的服务成功启动呢?今天我们就来从源码层面探讨下这个问题。

spring boot启动过程

在开始之前,我们先看这样一段代码:

@SpringBootApplication
public class DailyNoteApplication {

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

}

上面的这段代码就是我们最常见的spring boot启动的main方法,今天我们就从这个main方法开始,进入spring boot的世界。

springApplication

首先,在main方法内部执行了SpringApplication.run(DailyNoteApplication.class, args),这个方法有两个入参,一个是项目主类(当前类)的class,另一个就是main方法的args

这里先说下这个args参数,我们都知道spring boot是支持以命令行的方式注入配置信息的,它的实现就是依赖于这个args参数的。如果你将args删掉,项目也是可以正常启动的,只是你再也没办法通过命令行的方式注入配置了。关于这一块,我们之前在通过k8s启动spring boot项目的时候踩过坑,发现注入的参数不起作用,最后发现就是少了args

run方法

run方法内部,首先实例化了一个springApplication对象,然后又调用了另一个run方法:

springApplication实例化

我们先看springApplication的实例化过程:

前两个this都是简单的赋值,这里暂时先不过多研究,第三个this这里的WebApplicationType.deduceFromClasspath()是判断我们的服务器类别,在spring boot中,有两种服务器一种就是传统的sevlet,也就是基于tomcat(其中一种)这种,另一种就是reactive,也就是我们前面分享的webflux这种流式服务器。

紧接着是初始化ApplicationContextInitializerApplicationListener,这里主要是获取他们的spring工程实例,方便后续创建他们的实例,为了保证主流程的连贯性,我们暂时不看其方法内部实现。

最后一个赋值操作是找出包含main方法的类的className

run方法开始执行

下面我们看下springApplication实例的 run方法内部执行过程:

  • 创建了一个StopWatch对象,并调用它的start方法

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    
  • 设置javajava.awt.headless值,如果已经设置过就取系统设置的值,如果没有设置,则设置为true。这个是设置java的无头模式,启用之后,可以用计算能力来处理可视化操作(类似于用算力代替显卡渲染能力)

    configureHeadlessProperty();
    
  • 获取spring boot运行监听器

    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    
  • 解析控制台参数(args),获取应用参数

    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    
  • 配置需要忽略的bean信息,从源码中我们可以看出了,如果我们没有设置spring.beaninfo.ignorespring boot会给他默认true

    configureIgnoreBeanInfo(environment);
    //方法内部实现
    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
    		if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
    			Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
    			System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
    		}
    	}
    
  • 打印banner信息,这个banner就是spring boot启动的时候打印的哪个logo,那个是支持自定义的

    Banner printedBanner = printBanner(environment);
    
  • 创建spring boot容器,这里创建的时候会根据我们应用的不同,选择不同的容器

    context = createApplicationContext();
    

  • 创建spring工厂实例

    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    
  • 准备容器,这一步会进行初始化操作,把环境设置、系统参数、banner注入到容器中,并把容器绑定到监听器上

    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    
  • 刷新容器,这里其实进行了两步操作,一个是给我们的spring boot绑定SpringContextShutdownHook钩子函数,有了这个函数,我们就可以优雅地关闭spring boot了;另一个是刷新beanFactory,默认情况下spring boot为我们创建的是GenericApplicationContext容器,初始化完后,所有的对象都被初始化在它的beanFactory,为了确保其他组件也能拿到beanFactory中的内容,refreshContext方法内还进行了同步操作(直接copy给他们):

refreshContext(context);

从源码中可以很明显看出这一点:

  • 刷新完成后会执行afterRefresh方法,但是这个方法默认情况下是空的

    afterRefresh(context, applicationArguments);
    

  • 停止秒表。这个秒表的作用应该就是计时

    stopWatch.stop();
    
  • 调用监听器started方法,这方法修改了容器的状态。和前面starting方法不同的是,这个方法必须在beanfactory刷新后执行:

    listeners.started(context);
    

  • 运行容器中的runner,这里的runner主要有两类,一类是继承ApplicationRunner的,一类是继承CommandLineRunner。我猜测这个应该是为了方便我们实现更复杂的需求实现的,目前还没用到过,后面可以找时间研究下

    callRunners(context, applicationArguments);
    

  • 最后一步还是监听器的操作。这个方法最后将容器的状态改为ACCEPTING_TRAFFIC,表示可以接受请求

    listeners.running(context);
    

    到这里,spring boot就启动成功了。下面是整个run方法的源码,虽然不长,但是我感觉读起来还是有点吃力,想想自己模仿spring boot写的demo,真的是小巫见大巫。

总结

spring boot启动过程虽然看起来简单,用起来简单,但是当我一行一行看源码的时候,我觉得不简单,就好比老远看一棵大树,不就是一个直立的杆嘛,但是当你抵近看的时候,你会发现树干有树杈,树杈又有小树杈,总之看起来盘根错节的,总是感觉看不到树真实的样子。不过,随着后面我们不断地将spring boot的树叶、小树杈一一拿掉的时候,我相信我们会越来越清楚地看到spring boot这棵大树真实的样子。

今天的内容,其实如果有一张时序图,看起来就比较友好了,但是由于时间的关系,今天来不及做了,我们明天争取把时序图搞出来。

另外,后面我还会把今天一笔带过的方法尽可能详细地研究然后讲解的,我的目标就是由大到小(从树干到树杈,最后到树叶)地剖析spring boot的源码,最后把spring boot的核心技术梳理清楚。好了,今天就先到这里吧!

原文地址:https://www.cnblogs.com/caoleiCoding/p/15232215.html