SpringBoot启动时类加载器的补充

4,797 阅读1分钟

前提

透过《SpringBoot如何启动与初始化》一文我们知道SpringBoot会使用LaunchedURLClassLoader作为主要的类加载器,并且将其资源解析囊括了BOOT-INF/classes/BOOT-INF/lib/。 但是,如果通过IDE直接启动SpringBoot,还是使用AppClassLoader,但是会默认添加-classpath参数指定所有依赖的jar包.

补充

我们都知道,Class.forName(String)使用的是引用方类加载器来加载被引用类,按此情况,JDK的SPI ServiceLoader是无法加载AppClassLoader才能加载的类的(如classpath等),为此ServiceLoader必须使用其他类加载器才行.

在每条线程Thread里都有一个contextClassLoader,可以用来传递类加载器,子线程默认会使用父线程的ClassLoader.

我们都知道,主程序入口类所在的main线程,其contextClassLoader是AppClassLoader,main线程所创建的其他线程都能够获得AppClassLoader,这样整个应用程序都能加载所有类(第三方类用classpath引入).

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    // 设置contextClassLoader为AppClassLoader
    Thread.currentThread().setContextClassLoader(this.loader);
    // ...
}

因此,ServiceLoader使用了ContextClassLoader,即AppClassLoader来加载,就可以加载到应用程序的类了.

同理,SpringBoot将LaunchedURLClassLoader设置到main线程里,就可以使整个SpringBoot应用加载到所有类了.

protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
    // 设置LaunchedURLClassLoader到contextClassLoader,之后再调起SpringApplication的main方法
    Thread.currentThread().setContextClassLoader(classLoader);
    createMainMethodRunner(mainClass, args, classLoader).run();
}

public void run() throws Exception {
    // 使用这个类加载器来加载入口类
    Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
    Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
    mainMethod.invoke(null, new Object[] { this.args });
}