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