SpringBoot成长记2:从HelloWorld开始分析SpringBoot

file

上一节我们提到过,认识一个新技术的时候,通常是从一个入门的HelloWorld开始,之后阅读它的一些入门文档和书籍、视频,从而掌握它的基本使用。

这一节我就来带大家从HelloWorld开始,先摸清楚SpringBoot的核心脉络,之后再来逐步分析透彻SpringBoot,从而精通它。

从搭建HelloWorld入口开始分析SpringBoot

首先我们从官方的文档中搭建出一个2.2.2 版本的SpringBoot,增加了两个starter,mybatis-plus-boot-starter、spring-boot-starter-web,使用Maven进行项目和依赖管理,配置一个本地的mysql。相信这个对你们来说,都比较简单,我就不一一进行赘述了。

经过上面的基本搭建,你就会有类似一个下面的一个SpringBoot HelloWorld级别 的入口。

package org.mfm.learn.springboot;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("org.mfm.learn.springboot.mapper")
@SpringBootApplication
public class LearnSpringBootApplication {

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

}

通过上一节你知道SpringBoot定义了一个SpringApplication的web应用启动流程,入口通过一个java -jar的命令,执行main函数启动一个JVM进程,运行内部的tomcat监听一个默认8080的端口,提供web服务。

file

整个过程中第一个最关键的就是SpringBoot定义的SpringApplication,我们一起先来看下它是怎么创建new的。

SpringApplication的创建时核心组件图

SpringApplication的创建时的代码分析

在上面的示例代码中,main方法执行了 SpringApplication的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);
}

run方法核心入参就是main函数所在类+main函数args的参数,之后就直接创建了一个SpringApplication对象。让我们一起来看看这个SpringBoot定义的概念怎么创建的,创建时的核心组件又有哪些呢?

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

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication的创建时核心脉络

SpringApplication的创建核心脉络比较简单:

1)ResourceLoader 指明资源加载器,这个暂时不清楚是啥东西,默认是个null。

2)webApplicationType 推断当前web应用类型,通过一个deduceFromClasspath方法推断出的。

3)之后设置了setInitializers、setListeners两个列表,分别是一堆Initializer和Listener,都是通过getSpringFactoriesInstances方法获取的。

4)通过primarySources、mainApplicationClass记录了启动主要资源类,也就是之前HelloWorld中的LearnSpringBootApplication.class。

上面是我第一次看这个类的一个脉络后,脑中得到的结果。

file

你第一次看这里,肯定什么都不清楚,不知道每个变量有什么用,是干嘛的,没关系的,第一次,你只要熟悉它的脉络就可以。知道这里设置了两个集合变量Initializer和Listener,可以设置esourceLoader ,标记了一些类型和类,这就够了。

之后你有时间,再挨个去了解每个变量或者组件的作用就可以了,这个不还是先脉络后细节的思想,是吧?

SpringApplication的创建时的细节分析

你可以慢慢拆解上面的每一步,单独看看每一个组件大体是作什么的,这个就是细节的研究,可以一步一步来。

你可以研究下ResourceLoader 是个啥? 你可以看它的类注解后可以发现,这个ResourceLoader 类负责使用ClassLoader加载ClassPath下的class和各种配置文件的。(如果你不知道JVM的ClassLoader机制,主要加载什么,可以自己去baidu、google了解下)。这里你可以进一步思考下,它设计成了一个接口,可以实现不同的类加载器来加载资源。

webApplicationType 如何被推断的?就是根据几个静态变量定义的类全限定名称,根据classPath下是否存在对应的类,来推断出类型,使用了web-starter。默认推断出为Servlet类型的应用。

至于primarySources、mainApplicationClass这个两个变量记录了LearnSpringBootApplication.class, 大体是为了之后扫描自动配置等考虑的,表示从什么包名的哪一个类下启动的。

最后两个集合变量Initializer和Listener如何设置的,这块比较值得研究下。

基本原理是通过ClassLoader扫描了classPath下所有META-INF/spring.factories这个目录中的文件,通过指定的factoryType,也就是接口名称,获取对应的所有实现类,并且实例化成对象,返回成一个list列表。

比如factoryType=ApplicationContextInitializer 就返回这个接口在META-INF/spring.factories定义的所有的实现类,并实例化为一个列表List ApplicationContextInitializer 。

ApplicationListener同理,获取到了List ApplicationListener一个集合。

这里面其实有很多细节,使用了类加载器、缓存机制,反射机制等,有兴趣的同学可以仔细研究下。

这里以我们抓大放小思想,概括成一句话:通过工具方法通过classLoader获取classPath指定位置某个接口所有实现类的实例对象列表。

这里获取的是ApplicationContextInitializer、ApplicationListener这两个接口的实例对象列表。

细节中可以学到知识,脉络中一样可以学到知识,这个思想你一定要慢慢有。抓大放小的意思,更多的是让你知道重点和关键点,而不是让你丢弃细节,这两者并不冲突,这个一定要注意。

最后这里细节分析,画一个简单组件图小结下:

file

SpringApplication Run方法的脉络分析

熟悉了SpringApplication 的创建,接着我们该分析它的run方法了。

其实之前一节,我们介绍过SpringApplication 的启动流程。就是高度概括了run方法的核心脉络,run方法的核心其实核心就是下图蓝色的部分:

file

run方法脉络可以主要概括为:

1)自动装配配置

2)Spring容器的创建

3)web容器启动(Tomcat的启动)

然而在run方法的执行过程,肯定不会这么简单,过程中还掺杂了很多杂七杂八的逻辑,其中有意思的扩展点,也有值得吐槽的坑。这是每个框架都会有的优势劣势吧。我们先大体摸一下run方法的脉络,给大家介绍几个术语,不然之后可能会看不懂代码细节。

SpringApplication Run方法的脉络进一步分析

要想进一步分析run方法的脉络,首先需要熟悉几个术语,就有点像DDD的通用语言似的,懂了这些语言,理解SpringBoot和Spring才会更得心应手。

术语普及Context/BeanFactory/Environment

ConfigurableApplicationContext,容器通常称为ApplicationContext或者BeanFactory,context也简称为容器。ApplicationContext包装了BeanFactory,封装更高级的API而已。

ConfigurableEnvironment ,是配置文件的抽象,有关什么properties或者yml等配置文件的key-value值,都会封装成这个类的某个实现类。

熟悉了这些术语后,我们看一起看下SpringApplication 的run方法代码。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

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

上面的代码,主要就是执行了一堆方法,可以从方法名字看出,都是围绕Context、Environment这些术语。也就是围绕容器和配置文件组织的逻辑。

在整个Spring容器的创建,刷新,刷新之后穿插了很多逻辑。

另外,SpringBoot整个run方法中有几个很关键扩展点,设计SpringApplicationRunListeners、Runners等扩展入口。容器创建、刷新等也要各自的扩展点,对容器的增强扩展,如beanFactoryPostProcessor,对Bean的增加扩展,如beanPostProcessor。然而这些都是后话了。

我直接用一张图给大家概括了,上面run方法脉络:

(*黑色是直观的看出来的扩展逻辑,白色是run方法每个方法的字面理解,只是每一步有很多扩展点和做的事情比较多,让你感觉会有点云里雾里的。蓝色部分,概括了核心逻辑。也就是SpringBoot启动,说白了我们核心就是要找到这三点:自动装配配置、Spring容器的创建、web容器启动。)

file

这时候你一定要学会抓大放小的思想,之后带着这3个关键步骤,去理解SpringBoot,其他的实现可以单独来研究分析它的设计思路,比如各个扩展点的设计是如何考虑的,我们可以参考借鉴哪一些。这才是学习SpringBoot最最该学习的。

概括下就是,当一个技术看着比较复杂时,你应该顺着核心脉络理解原理,学习各个细节的亮点设计思想。不要陷入某一个细节,多思考才最重要。大家一定要记住这一点,在后续的成长记中,我会逐步带大家体验这一点的。

小结

好了,简单小结下。

主要思想学习了:

1)先脉络后细节的思想,抓大放小的思想,排除不重要的,分析最主要的。

2)细节中可以学到知识,脉络中一样可以学到知识,这个思想你一定要慢慢有。抓大放小的意思,更多的是让你知道重点和关键点,而不是让你丢弃细节,这两者并不冲突,这个一定要注意。

3)多思考才最重要。顺着核心脉络理解原理,学习各个细节的亮点设计思想,千万不能陷入知识本身。

主要知识学习了:

今天我们主要看了下SpringApplication的创建,它的核心组件有哪些,创建后执行的run方法,到底做了些什么,脉络是怎么样的。

熟悉了这些脉络,剩下的就简单了,逐步分析每个细节,看看每个细节有些值得我们学习的点,又有哪一些不太适合的点。

我们下期再见!

本文由博客一文多发平台 OpenWrite 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容