SpringBoot 类加载过程

1. Jar包目录分析

1.1 spring boot 2.0.0 项目打包成 jar后目录结构
├── META-INF
│   ├── MANIFEST.MF (Jar.Properties?)
├── BOOT-INF
│   ├── classes 项目源代码 
│   ├── lib  项目jar包
└── org
    └── springframework
        └── boot
            └── loader
                ├── ExecutableArchiveLauncher.class
                ├── JarLauncher.class
                ├── JavaAgentDetector.class
                ├── LaunchedURLClassLoader.class
                ├── Launcher.class
                ├── MainMethodRunner.class
                ├── ... 
1.2 MANIFEST.MF 文件
Manifest-Version: 1.0
Implementation-Title: TeamManager
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: MRZhao
Implementation-Vendor-Id: com.zhao
Spring-Boot-Version: 2.0.0.RELEASE
//以jar包形式启动的主函数 主启动器
Main-Class: org.springframework.boot.loader.JarLauncher
//自己项目 Main函数入口
Start-Class: com.zhao.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.3
Build-Jdk: 1.8.0_31
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
 ot-starter-parent/TeamManager

2.Archive的概念

1.Archive即归档文件,这个概念在linux下比较常见

2.Archive 略等价于 jar包或者文档目录的抽象

package org.springframework.boot.loader.archive;

public abstract interface Archive  extends Iterable<Entry>{
  //返回此jar包的url(定位符)
  public abstract URL getUrl()
  //获取lib下/jar包列表
  public abstract Manifest getManifest()
   
  public abstract List<Archive> getNestedArchives(EntryFilter paramEntryFilter)
  public static abstract interface EntryFilter {
    public abstract boolean matches(Archive.Entry paramEntry);
  }
    public static abstract interface Entry  {
    public abstract boolean isDirectory();
    public abstract String getName();
  }
}

3.JarLauncher

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:

class JarLauncher 
    extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher 
    extends Launcher

以demo-0.0.1-SNAPSHOT.jar创建一个Archive:
JarLauncher先找到自己所在的jar,即demo-0.0.1-SNAPSHOT.jar的路径,然后创建了一个Archive。

下面的代码展示了如何从一个类找到它的加载的位置的技巧:

 protected final Archive createArchive() throws Exception {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
        String path = (location == null ? null : location.getSchemeSpecificPart());
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if (!root.exists()) {
            throw new IllegalStateException(
                    "Unable to determine code source archive from " + root);
        }
        return (root.isDirectory() ? new ExplodedArchive(root)
                : new JarFileArchive(root));
    }

获取lib/下面的jar,并创建一个LaunchedURLClassLoader
JarLauncher创建好Archive之后,通过getNestedArchives函数来获取到demo-0.0.1-SNAPSHOT.jar/lib下面的所有jar文件,并创建为List。

注意上面提到,Archive都是有自己的URL的。

获取到这些Archive的URL之后,也就获得了一个URL[]数组,用这个来构造一个自定义的ClassLoader:LaunchedURLClassLoader。

创建好ClassLoader之后,再从MANIFEST.MF里读取到Start-Class,即com.example.SpringBootDemoApplication,然后创建一个新的线程来启动应用的Main函数。

  /**
     * Launch the application given the archive file and a fully configured classloader.
     */
    protected void launch(String[] args, String mainClass, ClassLoader classLoader)
            throws Exception {
        Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
        Thread runnerThread = new Thread(runner);
        runnerThread.setContextClassLoader(classLoader);
        runnerThread.setName(Thread.currentThread().getName());
        runnerThread.start();
    }

    /**
     * Create the {@code MainMethodRunner} used to launch the application.
     */
    protected Runnable createMainMethodRunner(String mainClass, String[] args,
            ClassLoader classLoader) throws Exception {
        Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS);
        Constructor<?> constructor = runnerClass.getConstructor(String.class,
                String[].class);
        return (Runnable) constructor.newInstance(mainClass, args);
    }
LaunchedURLClassLoader

LaunchedURLClassLoader和普通的URLClassLoader的不同之处是,它提供了从Archive里加载.class的能力。

结合Archive提供的getEntries函数,就可以获取到Archive里的Resource。当然里面的细节还是很多的,下面再描述。

spring boot应用启动流程总结

看到这里,可以总结下Spring Boot应用的启动流程:

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

推荐阅读更多精彩内容