转载

面试官:SpringBoot jar 可执行原理,知道吗?

回复“ 1024 ”或 “ 面试题获取学习资料

文章篇幅较长,但是包含了SpringBoot 可执行jar包从头到尾的原理,请读者耐心观看。同时文章是基于 SpringBoot-2.1.3 进行分析。涉及的知识点主要包括 Maven的生命周期以及自定义插件,JDK提供关于jar包的工具类以及Springboot如何扩展,最后是自定义类加载器

spring-boot-maven-plugin

SpringBoot 的可执行jar包又称 fat jar ,是 包含所有第三方依赖的 jar 包 ,jar 包中嵌入了除 java 虚拟机以外的所有依赖,是一个 all-in-one jar 包。普通插件 maven-jar-plugin 生成的包和 spring-boot-maven-plugin 生成的包之间的直接区别,是 fat jar 中主要增加了两部分,第一部分是 lib目录 ,存放的是Maven依赖的jar包文件,第二部分是 spring boot loader 相关的类。

也就是说想要知道 fat jar 是如何生成的,就必须知道 spring-boot-maven-plugin 工作机制,而 spring-boot-maven-plugin 属于自定义插件,因此我们又必须知道, Maven的自定义插件是如何工作的

Maven的自定义插件

Maven 拥有三套相互独立的生命周期: cleandefaultsite , 而每个生命周期包含一些 phase 阶段, 阶段是有顺序的, 并且后面的阶段依赖于前面的阶段。生命周期的阶段 phase 与插件的目标 goal 相互绑定,用以完成实际的构建任务。

repackage 目标对应的将执行到 org.springframework.boot.maven.RepackageMojo#execute ,该方法的主要逻辑是调用了 org.springframework.boot.maven.RepackageMojo#repackage

我们关心一下 org.springframework.boot.maven.RepackageMojo#getRepackager 这个方法,知道 Repackager 是如何生成的,也就大致能够推测出内在的打包逻辑。

layout 我们可以将之翻译为文件布局,或者目录布局,代码一看清晰明了,同时我们需要关注,也是下一个重点关注对象 org.springframework.boot.loader.JarLauncher ,从名字推断,这很可能是返回可执行 jar 文件的启动类。

MANIFEST.MF文件内容

repackager 生成的MANIFEST.MF文件为以上信息,可以看到两个关键信息 Main-ClassStart-Class 。我们可以进一步,程序的启动入口并不是我们SpringBoot中定义的 main ,而是 JarLauncher#main ,而再在其中利用反射调用定义好的 Start-Classmain 方法

JarLauncher

重点类介绍

1、 java.util.jar.JarFile JDK工具类提供的读取 jar 文件

2、 org.springframework.boot.loader.jar.JarFile Springboot-loader 继承JDK提供 JarFile

3、 java.util.jar.JarEntry DK工具类提供的``jar```文件条目

4、 org.springframework.boot.loader.jar.JarEntry Springboot-loader 继承JDK提供 JarEntry

5、 org.springframework.boot.loader.archive.Archive Springboot抽象出来的统一访问资源的层

6、 JarFileArchive jar包文件的抽象

7、 ExplodedArchive 文件目录

这里重点描述一下 JarFile 的作用,每个 JarFileArchive 都会对应一个 JarFile 。在构造的时候会解析内部结构,去获取 jar 包里的各个 文件文件夹 类。我们可以看一下该类的注释。

jar 里的资源分隔符是 !/ ,在JDK提供的 JarFile URL只支持一个’!/‘,而Spring boot扩展了这个协议,让它支持多个’!/‘,就可以表示jar in jar、jar in directory、fat jar的资源了。

自定义类加载机制

最基础:Bootstrap ClassLoader(加载JDK的/lib目录下的类)

次基础:Extension ClassLoader(加载JDK的/lib/ext目录下的类)

普通:Application ClassLoader(程序自己classpath下的类)

首先需要关注 双亲委派机制 很重要的一点是,如果一个类可以被委派最基础的ClassLoader加载,就不能让高层的ClassLoader加载,这样是为了范围错误的引入了非JDK下但是类名一样的类。其二,如果在这个机制下,由于 fat jar 中依赖的各个第三方 jar 文件,并不在程序自己 classpath 下,也就是说,如果我们采用双亲委派机制的话, 根本获取不到我们所依赖的jar包,因此我们需要修改双亲委派机制的查找class的方法,自定义类加载机制

先简单的介绍Springboot2中 LaunchedURLClassLoader ,该类继承了 java.net.URLClassLoader ,重写了 java.lang.ClassLoader#loadClass(java.lang.String, boolean) ,然后我们再探讨他是如何修改双亲委派机制。

在上面我们讲到Spring boot支持多个’!/‘以表示多个jar,而我们的问题在于,如何解决查找到这多个jar包。我们看一下 LaunchedURLClassLoader 的构造方法。

urls 注释解释道 theURLsfromwhich to load classesandresources ,即fat jar包依赖的所有类和资源,将该 urls 参数传递给父类 java.net.URLClassLoader ,由父类的 java.net.URLClassLoader#findClass 执行查找类方法,该类的查找来源即构造方法传递进来的urls参数

方法 super.loadClass(name,resolve) 实际上会回到了 java.lang.ClassLoader#loadClass(java.lang.String, boolean) ,遵循双亲委派机制进行查找类,而 BootstrapClassLoaderExtensionClassLoader 将会查找不到fat jar依赖的类,最终会来到 ApplicationClassLoader ,调用 java.net.URLClassLoader#findClass

如何真正的启动

Springboot2和Springboot1的最大区别在于,Springboo1会新起一个线程,来执行相应的 反射调用逻辑 ,而SpringBoot2则去掉了构建新的线程这一步。方法是 org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader) 反射调用逻辑比较简单,这里就不再分析,比较关键的一点是,在调用 main 方法之前,将当前线程的上下文类加载器设置成 LaunchedURLClassLoader

Demo

Maven

总结

对于源码分析,这次的较大收获则是不能一下子去追求弄懂源码中的每一步代码的逻辑,即便我知道该方法的作用。我们需要搞懂的是关键代码,以及涉及到的知识点。我从Maven的自定义插件开始进行追踪,巩固了对Maven的知识点,在这个过程中甚至了解到JDK对jar的读取是有提供对应的工具类。最后最重要的知识点则是自定义类加载器。整个代码下来并不是说代码究竟有多优秀,而是要学习他因何而优秀。

敬请关注「搜云库技术团队」微信公众号,获取最新文章

4折优惠,满600元减350元,当当网计算机图书

来源: juejin.im/post/5d2d6812e51d45777b1a3e5a

整编:搜云库技术团队,欢迎广大技术人员投稿

如果对本文的内容有疑问,请在文章留言区留言,谢谢。

》》》福利 + 程序员工作内推群《《《

2000道 互联网Java工程师  面试题  共768页.pdf

关注公众号并回复: 面试题   无套路获取

更多技术干货

推荐: 近300篇:历史技术文章,20大分类整理  

面试官:SpringBoot jar 可执行原理,知道吗?

原文  http://mp.weixin.qq.com/s?__biz=MzA3MTUzOTcxOQ==&mid=2452967612&idx=1&sn=da6ab8e35a28bfc507882c041661a9e2
正文到此结束
Loading...