1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

从代码层面看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的核心技术梳理清楚。好了,今天就先到这里吧!

posted @ 2021-09-06 08:31  云中志  阅读(99)  评论(0编辑  收藏  举报