不得不说 SpringBoot 太杂乱了,我原本只想研讨一下 SpringBoot 最简略的 HelloWorld 程序是如何从 main 办法一步一步跑起来的,可是这却是一个适当深的坑。你可以试着沿着调用栈代码一层一层的深入进去,假如你不打断点,你根本不知道接下来程序会往哪里活动。这个不同于我研讨过去的 Go 言语、Python 言语结构,它们一般都十分直接了当,规划上清晰易懂,代码写起来简略,里边的完成同样也很简略。可是 SpringBoot 不是,它的外表轻巧简略,可是它的里边就像一只巨大的怪兽,这只怪兽有千百只脚把自己缠绕在一同,把爱研讨源码的读者绕的晕头转向。可是这 Java 编程的世界 SpringBoot 就是老大哥,你却不得不服。即便你的心中有千万头草泥马在奔驰,可是它就是天下第一。假如你是一个学院派的程序员,看到这种现象你会怀疑人生,你不得不接受一个规则 —— 受市场最欢迎的未必就是规划的最好的,里边夹杂着太多其它的非理性要素。
经过了一番痛苦的摧残,我还是把 SpringBoot 的运转原理摸清楚了,这儿分享给大家。
一、Hello World
首要咱们看看 SpringBoot 简略的 Hello World 代码,就两个文件 HelloControll.java 和 Application.java,运转 Application.java 就可以跑起来一个简略的 RESTFul Web 服务器了。
当我打开浏览器看到服务器正常地将输出呈现在浏览器的时分,我不由大喊 —— SpringBoot 真他妈太简略了。
可是问题来了,在 Application 的 main 办法里我压根没有任何当地引证 HelloController 类,那么它的代码又是如何被服务器调用起来的呢?这就需求深入到 SpringApplication.run() 办法中看个究竟了。不过即便不看代码,咱们也很容易有这样的猜想,SpringBoot 肯定是在某个当地扫描了当时的 package,将带有 RestController 注解的类作为 MVC 层的 Controller 主动注册进了 Tomcat Server。
还有一个让人不爽的当地是 SpringBoot 发动太慢了,一个简略的 Hello World 发动居然还需求长达 5 秒,要是再杂乱一些的项目这样龟漫的发动速度那真是不好幻想了。
再诉苦一下,这个简略的 HelloWorld 虽然 pom 里只装备了一个 maven 依靠,可是传递下去,它总共依靠了 36 个 jar 包,其中以 spring 最初的 jar 包有 15 个。说这是依靠地狱真一点不为过。
批判到这儿就差不多了,下面就要正是进入主题了,看看 SpringBoot 的 main 办法到底是如何跑起来的。
二、SpringBoot 的仓库
了解 SpringBoot 运转的最简略的办法就是看它的调用仓库,下面这个发动调用仓库还不是太深,我没什么可诉苦的。
接下来再看看运转时仓库,看看一个 HTTP 恳求的调用栈有多深。不看不知道一看吓了一大跳!
我通过将 IDE 窗口全屏化,并将其它的控制台窗口源码窗口统统最小化,总算勉强一个屏幕装下了整个调用仓库。
不过转念一想,这也不怪 SpringBoot,绝大多数都是 Tomcat 的调用仓库,跟 SpringBoot 相关的只要不到 10 层。
三、探究 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 并运转。
又一个问题来了,当 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 值得欣赏的点之一。
四、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 这只怪兽,可是很无法,这个世界人人都在运用它。这就比如老人们常常告诫年轻人的那句话:假如你改变不了世界,那就先习惯这个世界吧!