Spring Boot这只怪物究竟是如何跑起来的?


不得不说 SpringBoot 太复杂了,我原本只想研究一下 SpringBoot 最简单的 HelloWorld 程序是如何从 main 方法一步一步跑起来的,可是这倒是一个至关深的坑。你能够试着沿着调用栈代码一层一层的深刻进去,若是你不打断点,你根本不知道接下来程序会往哪里流动。这个不一样于我研究过去的 Go 语言、Python 语言框架,它们一般都很是直接了当,设计上清晰易懂,代码写起来简单,里面的实现一样也很简单。可是 SpringBoot 不是,它的外表轻巧简单,可是它的里面就像一只巨大的怪兽,这只怪兽有千百只脚把本身缠绕在一块儿,把爱研究源码的读者绕的晕头转向。可是这 Java 编程的世界 SpringBoot 就是老大哥,你却不得不服。即便你的心中有千万头草泥马在奔跑,可是它就是天下第一。若是你是一个学院派的程序员,看到这种现象你会怀疑人生,你不得不接受一个规则 —— 受市场最欢迎的未必就是设计的最好的,里面夹杂着太多其它的非理性因素。
java

通过了一番痛苦的折磨,我仍是把 SpringBoot 的运行原理摸清楚了,这里分享给你们。程序员

1、Hello World

首先咱们看看 SpringBoot 简单的 Hello World 代码,就两个文件 HelloControll.java 和 Application.java,运行 Application.java 就能够跑起来一个简单的 RESTFul Web 服务器了。
面试

当我打开浏览器看到服务器正常地将输出呈如今浏览器的时候,我不由大呼 —— SpringBoot 真他妈太简单了。
spring

可是问题来了,在 Application 的 main 方法里我压根没有任何地方引用 HelloController 类,那么它的代码又是如何被服务器调用起来的呢?这就须要深刻到 SpringApplication.run() 方法中看个究竟了。不过即便不看代码,咱们也很容易有这样的猜测,SpringBoot 确定是在某个地方扫描了当前的 package,将带有 RestController 注解的类做为 MVC 层的 Controller 自动注册进了 Tomcat Server。编程

小编分类整理了许多java进阶学习材料和BAT面试题,须要资料的请加JAVA高阶学习Q群:8515318105;就能领取2019年java架构师进阶学习资料和BAT面试题。浏览器

还有一个让人不爽的地方是 SpringBoot 启动太慢了,一个简单的 Hello World 启动竟然还须要长达 5 秒,要是再复杂一些的项目这样龟漫的启动速度那真是很差想象了。缓存

再抱怨一下,这个简单的 HelloWorld 虽然 pom 里只配置了一个 maven 依赖,可是传递下去,它一共依赖了 36 个 jar 包,其中以 spring 开头的 jar 包有 15 个。说这是依赖地狱真一点不为过。
springboot

批评到这里就差很少了,下面就要正是进入主题了,看看 SpringBoot 的 main 方法究竟是如何跑起来的。服务器

2、SpringBoot 的堆栈

了解 SpringBoot 运行的最简单的方法就是看它的调用堆栈,下面这个启动调用堆栈还不是太深,我没什么可抱怨的。
架构

接下来再看看运行时堆栈,看看一个 HTTP 请求的调用栈有多深。不看不知道一看吓了一大跳!

我经过将 IDE 窗口全屏化,并将其它的控制台窗口源码窗口通通最小化,总算勉强一个屏幕装下了整个调用堆栈。

不过转念一想,这也不怪 SpringBoot,绝大多数都是 Tomcat 的调用堆栈,跟 SpringBoot 相关的只有不到 10 层。

3、探索 ClassLoader

SpringBoot 还有一个特点的地方在于打包时它使用了 FatJar 技术将全部的依赖 jar 包一块儿放进了最终的 jar 包中的 BOOT-INF/lib 目录中,当前项目的 class 被统一放到了 BOOT-INF/classes 目录中。

这不一样于咱们平时常用的 maven shade 插件,将全部的依赖 jar 包中的 class 文件解包出来后再密密麻麻的塞进统一的 jar 包中。下面咱们将 springboot 打包的 jar 包解压出来看看它的目录结构。

这种打包方式的优点在于最终的 jar 包结构很清晰,全部的依赖一目了然。若是使用 maven shade 会将全部的 class 文件混乱堆积在一块儿,是没法看清其中的依赖。而最终生成的 jar 包在体积上两也者几乎是相等的。

在运行机制上,使用 FatJar 技术运行程序是须要对 jar 包进行改造的,它还须要自定义本身的 ClassLoader 来加载 jar 包里面 lib 目录中嵌套的 jar 包中的类。咱们能够对比一下二者的 MANIFEST 文件就能够看出明显差别:

SpringBoot 将 jar 包中的 Main-Class 进行了替换,换成了 JarLauncher。还增长了一个 Start-Class 参数,这个参数对应的类才是真正的业务 main 方法入口。咱们再看看这个 JarLaucher 具体干了什么:

从源码中能够看出 JarLaucher 建立了一个特殊的 ClassLoader,而后由这个 ClassLoader 来另启一个单独的线程来加载 MainClass 并运行。

小编分类整理了许多java进阶学习材料和BAT面试题,须要资料的请加JAVA高阶学习Q群:8515318105;就能领取2019年java架构师进阶学习资料和BAT面试题。

又一个问题来了,当 JVM 遇到一个不认识的类,BOOT-INF/lib 目录里又有那么多 jar 包,它是如何知道去哪一个 jar 包里加载呢?咱们继续看这个特别的 ClassLoader 的源码:

这里的 rootClassLoader 就是双亲委派模型里的 ExtensionClassLoader ,JVM 内置的类会优先使用它来加载。若是不是内置的就去查找这个类对应的 Package。

ClassLoader 会在本地缓存包名和 jar包路径的映射关系,若是缓存中找不到对应的包名,就必须去 jar 包中挨个遍历搜寻,这个就比较缓慢了。不过同一个包名只会搜寻一次,下一次就能够直接从缓存中获得对应的内嵌 jar 包路径。

深层 jar 包的内嵌 class 的 URL 路径长下面这样,使用感叹号 ! 分割:

jar:file:/workspace/springboot-demo/target/application.jar!/BOOT-INF/lib/snakeyaml-1.19.jar!/org/yaml/snakeyaml/Yaml.class

不过这个定制的 ClassLoader 只会用于打包运行时,在 IDE 开发环境中 main 方法仍是直接使用系统类加载器加载运行的。

不得不说,SpringbootLoader 的设计仍是颇有意思的,它自己很轻量级,代码逻辑很独立没有其它依赖,它也是 SpringBoot 值得欣赏的点之一。

4、HelloController 自动注册

还剩下最后一个问题,那就是 HelloController 没有被代码引用,它是如何注册到 Tomcat 服务中去的?它靠的是注解传递机制。

SpringBoot 深度依赖注解来完成配置的自动装配工做,它本身发明了几十个注解,确实严重增长了开发者的心智负担,你须要仔细阅读文档才能知道它是用来干吗的。Java 注解的形式和功能是分离的,它不一样于 Python 的装饰器是功能性的,Java 的注解就比如代码注释,自己只有属性,没有逻辑,注解相应的功能由散落在其它地方的代码来完成,须要分析被注解的类结构才能够获得相应注解的属性。

那注解是又是如何传递的呢?

首先 main 方法能够看到的注解是 SpringBootApplication,这个注解又是由ComponentScan 注解来定义的,ComponentScan 注解会定义一个被扫描的包名称,若是没有显示定义那就是当前的包路径。SpringBoot 在遇到 ComponentScan 注解时会扫描对应包路径下面的全部 Class,根据这些 Class 上标注的其它注解继续进行后续处理。当它扫到 HelloController 类时发现它标注了 RestController 注解。

而 RestController 注解又标注了 Controller 注解。SpringBoot 对 Controller 注解进行了特殊处理,它会将 Controller 注解的类当成 URL 处理器注册到 Servlet 的请求处理器中,在建立 Tomcat Server 时,会将请求处理器传递进去。HelloController 就是如此被自动装配进 Tomcat 的。

扫描处理注解是一个很是繁琐肮脏的活计,特别是这种用注解来注解注解(绕口)的高级使用方法,这种方法要少用慎用。SpringBoot 中有大量的注解相关代码,企图理解这些代码是乏味无趣的没有必要的,它只会把你的原本清醒的脑壳搞晕。SpringBoot 对于习惯使用的同窗来讲它是很是方便的,可是其内部实现代码不要轻易模仿,那绝对算不上模范 Java 代码。

最后老钱表示本身真的很讨厌 SpringBoot 这只怪兽,可是很无奈,这个世界人人都在使用它。这就比如老人们经常告诫年轻人的那句话:若是你改变不了世界,那就先适应这个世界吧!

小编分类整理了许多java进阶学习材料和BAT面试题,须要资料的请加JAVA高阶学习Q群:8515318105;就能领取2019年java架构师进阶学习资料和BAT面试题。

相关文章
相关标签/搜索