Spring Boot 启动原理思维导图

Spring Boot 源码之启动原理 - 图1

SpringBoot 如何通过 jar 启动?

使用 -jar 参数时,后面的参数是 jar 文件的名字;该 jar 文件中包含的是 class 和资源文件;在 mainfest 文件中有 Main-Class 的定义;Main-Class 指定了整个应用的启动类;

Start-Class 指定的是代码中的唯一类,也是真正的应用启动类

jar 包目录结构:

  1. spring-boot-learn-0.0.1-SNAPSHOT
  2. ├── META-INF
  3. └── MANIFEST.MF
  4. ├── BOOT-INF
  5. ├── classes
  6. └── 应用程序类
  7. └── lib
  8. └── 第三方依赖jar
  9. └── org
  10. └── springframework
  11. └── boot
  12. └── loader
  13. └── springboot启动程序

META-INF 内容:

  1. Manifest-Version: 1.0
  2. Implementation-Title: spring-learn
  3. Implementation-Version: 0.0.1-SNAPSHOT
  4. Start-Class: com.zrk.Application
  5. Spring-Boot-Classes: BOOT-INF/classes/
  6. Spring-Boot-Lib: BOOT-INF/lib/
  7. Build-Jdk-Spec: 1.8
  8. Spring-Boot-Version: 2.1.5.RELEASE
  9. Created-By: Maven Archiver 3.4.0
  10. Main-Class: org.springframework.boot.loader.JarLauncher

Archive 概念:

  • archive 即归档文件,在 linux 下比较常见
  • 通常就是一个 tar/zip 格式的压缩包
  • jar 是 zip 格式

SpringBoot 抽象了 Archive 的概念;一个 Archive 可以是 jar,可以是一个文件目录,可以抽象为统一访问资源的逻辑层;源码如下:

  1. public interface Archive extends Iterable<Archive.Entry> {
  2. // 获取该归档的url
  3. URL getUrl() throws MalformedURLException;
  4. // 获取jar!/META-INF/MANIFEST.MF或[ArchiveDir]/META-INF/MANIFEST.MF
  5. Manifest getManifest() throws IOException;
  6. // 获取jar!/BOOT-INF/lib/*.jar或[ArchiveDir]/BOOT-INF/lib/*.jar
  7. List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
  8. }

SpringBoot jar 应用启动流程解析

  1. SpringBoot 应用打包后,生成一个 Fat jar,包含了应用依赖的 jar 包和 SpringBoot loader 相关的类
  2. Fat jar 的启动 Main 函数是 JarLauncher,它负责创建一个 LaunchedURLClassLoader 来加载/lib 下面的 jar,并以一个新线程启动应用的 Main 函数

    那么,ClassLoader 是如何读取到 Resource,又有哪些能力? 查找资源和读取资源的能力;对应 api

  1. public URL findResource(String name)
  2. public InputStream getResourceAsStream(String name)

SpringBoot 构造 LaunchedURLClassLoader 时,传递了一个 URL[] 数组,数组里是 lib 目录下的 jar 的 URL;对于一个 URL,JDK 或者 ClassLoader 读取其内容流程如下:

  1. LaunchedURLClassLoader.loadClass
  2. URL.getContent()
  3. URL.openConnection()
  4. Handler.openConnection(URL)

最终调用的是 JarURLConnection 的 getInputStream() 函数:

  1. //org.springframework.boot.loader.jar.JarURLConnection
  2. @Override
  3. public InputStream getInputStream() throws IOException {
  4. connect();
  5. if (this.jarEntryName.isEmpty()) {
  6. throw new IOException("no entry name specified");
  7. }
  8. return this.jarEntryData.getInputStream();
  9. }

从一个 URL,到最终读取到 URL 里的内容,整个过程是比较复杂的:

  1. SpringBoot 注册了一个 Handler 来处理”jar:”这种协议的 URL
  2. SpringBoot 扩展了 JarFile 和 JarURLConnection,内部处理 jar in jar 的情况
  3. 在处理多重 jar in jar 的 URL 时,SpringBoot 会循环处理,并缓存已经加载到的 JarFile
  4. 对于多重 jar in jar,实际上是解压到了临时目录来处理
  5. 在获取 URL 的 InputStream 时,最终获得到的是 JarFile 里的 JarEntryData

URLClassLoader 是如何 getResource 的呢?

URLClassLoader 在构造时,有 URL[] 数组参数,它内部会用这个数组来构造一个 URLClassPath:URLClassPath ucp = new URLClassPath(urls);在 URLClassPath 内部会为这些 urls 都构造一个 Loader,然后在 getResource 时会重这些 Loader 里一个个去尝试获取;若获取成功,则包装为一个 Resource:

  1. Resource getResource(final String name, boolean check) {
  2. final URL url;
  3. try {
  4. url = new URL(base, ParseUtil.encodePath(name, false));
  5. } catch (MalformedURLException e) {
  6. throw new IllegalArgumentException("name");
  7. }
  8. final URLConnection uc;
  9. try {
  10. if (check) {
  11. URLClassPath.check(url);
  12. }
  13. uc = url.openConnection();
  14. InputStream in = uc.getInputStream();
  15. if (uc instanceof JarURLConnection) {
  16. /* Need to remember the jar file so it can be closed
  17. * in a hurry.
  18. */
  19. JarURLConnection juc = (JarURLConnection)uc;
  20. jarfile = JarLoader.checkJar(juc.getJarFile());
  21. }
  22. } catch (Exception e) {
  23. return null;
  24. }
  25. return new Resource() {
  26. public String getName() { return name; }
  27. public URL getURL() { return url; }
  28. public URL getCodeSourceURL() { return base; }
  29. public InputStream getInputStream() throws IOException {
  30. return uc.getInputStream();
  31. }
  32. public int getContentLength() throws IOException {
  33. return uc.getContentLength();
  34. }
  35. };
  36. }
  37. JarURLConnection juc = (JarURLConnection)uc;