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

44次阅读

共计 6820 个字符,预计需要花费 18 分钟才能阅读完成。

上一节咱们提到过,意识一个新技术的时候,通常是从一个入门的 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 服务。

整个过程中第一个最要害的就是 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。

下面是我第一次看这个类的一个脉络后,脑中失去的后果。

你第一次看这里,必定什么都不分明,不晓得每个变量有什么用,是干嘛的,没关系的,第一次,你只有相熟它的脉络就能够。晓得这里设置了两个汇合变量 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 这两个接口的实例对象列表。

细节中能够学到常识,脉络中一样能够学到常识,这个思维你肯定要缓缓有。抓大放小的意思,更多的是让你晓得重点和关键点,而不是让你抛弃细节,这两者并不抵触,这个肯定要留神。

最初这里细节剖析,画一个简略组件图小结下:

SpringApplication Run 办法的脉络剖析

相熟了 SpringApplication 的创立,接着咱们该剖析它的 run 办法了。

其实之前一节,咱们介绍过 SpringApplication 的启动流程。就是高度概括了 run 办法的外围脉络,run 办法的外围其实外围就是下图蓝色的局部:

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 容器启动。)

这时候你肯定要学会 抓大放小的思维,之后带着这 3 个关键步骤,去了解 SpringBoot,其余的实现能够独自来钻研剖析它的设计思路,比方各个扩大点的设计是如何思考的,咱们能够参考借鉴哪一些。这才是学习 SpringBoot 最最该学习的。

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

小结

好了,简略小结下。

次要思维学习了:

1)先脉络后细节的思维,抓大放小的思维,排除不重要的,剖析最次要的。

2)细节中能够学到常识,脉络中一样能够学到常识,这个思维你肯定要缓缓有。抓大放小的意思,更多的是让你晓得重点和关键点,而不是让你抛弃细节,这两者并不抵触,这个肯定要留神。

3)多思考才最重要。顺着外围脉络了解原理,学习各个细节的亮点设计思维,千万不能陷入常识自身。

次要常识学习了:

明天咱们次要看了下 SpringApplication 的创立,它的外围组件有哪些,创立后执行的 run 办法,到底做了些什么,脉络是怎么样的。

相熟了这些脉络,剩下的就简略了,逐渐剖析每个细节,看看每个细节有些值得咱们学习的点,又有哪一些不太适宜的点。

咱们下期再见!

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

正文完
 0