暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring Boot FatJar实现原理

CodingWithFun 2020-01-07
1205

使用maven或gradle插件将Spring Boot工程打包之后,生成的jar是可以直接运行的,这个jar被称为FatJar。我们探究一下,这个jar是如何来工作的。

Spring Boot FatJar的构成

jar本身是一个zip文件,随便找一个SpringBoot工程的jar解压,会看到包含三个文件夹:BOOT-INF、META-INF、org。

BOOT-INF文件夹包含了应用本身的代码以及依赖的第三方jar包,META-INF包含重要的清单文件MANIFEST.MF,org是springBoot的启动器。

我们先打开MANIFEST.MF文件,会发现该jar的Main-Class是org.springframework.boot.loader.JarLauncher,并不是我们自己写的类,但Start-Class的值是咱们自己开发的包含SpringBootApplication注解的入口类。下面我们来阅读一下JarLauncher的源码。

JarLauncher类分析

JarLauncher默认是不再classpath下的,要想用IDE看到源码需加入依赖spring-boot-loader。之后就可以打开其源码了。看源码之前,咱们先看一下它的JavaDoc:

Launcher for JAR based archives. This launcher assumes that dependency jars are included inside a BOOT-INF/lib directory and that application classes are included inside a BOOT-INF/classes directory.

里面描述的很清楚,这个启动器假定第三方依赖在/BOOT-INF/lib下,应用的类位于/BOOT-INF/classes下。这就是为什么打包之后是以上所说的目录结构。

JarLauncher的继承关系如下:

我们看其main方法,该方法new一个JarLauncher,调用父类的lanuch方法。在其父类ExecutableArchiveLauncher构造方法中会创建archive字段,其代表当前的FatJar,并实现了迭代器,可遍历其所有文件。我们进入Launcher的launch方法,这个函数有三行代码,其中第二行创建了一个自定义的LaunchedURLClassLoader,其加载类的路径就是/BOOT-INF/lib与/BOOT-INF/classes。

1protected void launch(String[] args) throws Exception {
2    JarFile.registerUrlProtocolHandler();
3    ClassLoader classLoader = createClassLoader(getClassPathArchives());
4    launch(args, getMainClass(), classLoader);
5}

SpringBoot为何要自己定义一个ClassLoader呢?这是由于默认情况下,系统类加载器AppClassLoader是不会加载jar包当中的jar文件以及嵌套目录中的类的,也就是说BOOT-INF目录下的类都无法加载。但Spring通过自定义ClassLoader的方式解决了该问题。


第三行代码中getMainClass()方式是从jar包MANIFEST.MF文件中获取Start-Class的值,也就是应用代码入口。

 1@Override
2protected String getMainClass() throws Exception {
3    Manifest manifest = this.archive.getManifest();
4    String mainClass = null;
5    if (manifest != null) {
6        mainClass = manifest.getMainAttributes().getValue("Start-Class");
7    }
8    if (mainClass == null) {
9        throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
10    }
11    return mainClass;
12}

接着进入重载的launch方法,这里有一行特别重要的代码Thread.currentThread().setContextClassLoader(classLoader)。

1    protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
2        Thread.currentThread().setContextClassLoader(classLoader);
3        createMainMethodRunner(mainClass, args, classLoader).run();
4    }

将当前线程的上下文类加载器换成自定义的ClassLoader,这样当前线程及其子线程加载新的类都是使用SpringBoot自定义的类加载器。随后定义了MainMethodRunner对象来运行应用入口代码。

可以看到,它是先用上下文ClassLoader加载应用入口类,通过反射的方式调用main方法。

 1public class MainMethodRunner {
2
3    private final String mainClassName;
4
5    private final String[] args;
6
7    public void run() throws Exception {
8        Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
9        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
10        mainMethod.invoke(nullnew Object[] { this.args });
11    }
12
13}

总结

FatJar中分成三个目录,分别存放应用程序class与依赖、jar配置清单、SpringBoot自定义的启动器;SpringBoot通过自定义类加载器的方式非常巧妙地实现了jar in jar的class加载;线程加载的类都使用上下文加载器,默认是AppClassLoader,如需加载特殊目录或实现类的隔离可自定义加载器;


文章转载自CodingWithFun,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论