Springboot - Fat Jar
传送:
导读:
springboot FatJar 的设计,打破了标准jar的结构,在jar包内携带了其所依赖的jar包,通过jar中的main方法创建自己的类加载器,来识别加载运行其不规范的目录下的代码和依赖.
初识jar包结构
# JAR包中的MANIFEST.MF文件详解以及编写规范
打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录,这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息
其中 Main-Class 定义jar文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件。
Spring-Boot-Version: 2.1.3.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.rock.springbootlearn.SpringbootLearnApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk: 1.8.0_131
Main-Class: org.springframework.boot.loader.JarLauncher
运行时执行JarLauncher的Main方法
非开发阶段,是使用java -jar xxx.jar的方式来运行springboot程序.
这种情况下,springboot应用真实的启动类并不是我们所定义的带有main方法的类,而是
JarLauncher类.这个类在spring-boot-loader-***.jar包中,在项目中加入maven依赖,以便查看源码和远程调试.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
可找到这个JarLauncher类的源码.
image.png
这个spring-boot-loader包中的内容与springboot的运行jar包中的一部分内容几乎一样.
JarLauncher在jar中的位置如下:
image.png
因jar规范要求Main-Class所指定的类,必须位于jar包的顶层目录下,即org.springframework.boot.loader.JarLauncher 这个org必须位于jar包中的第一级目录,不能嵌套的其他的目录下.所以只能将spring-boot-loader这个jar包的内容拷贝出来,而不是整个jar直接放置于执行Jar中.
程序依赖的携带
image.png上边JarLauncher的这个org.xxx 以及 META-INF 这两个目录是符合jar包规范的.但是BOOT-INF这个目录里边类似我们开发中的一些用法,程序.class文件在classes目录下,依赖jar包在lib目录下.
按照jar包规范.jar中不能有jar包的情况下,.class文件应该按照spring-boot-loader这样顶级目录的方式,
从包内携带依赖角度来梳理,类似maven-shade-plugin 的做法,对于重名(全限定名)的类会出现覆盖.为了避免覆盖的情况,依赖原始jar包;要想让这些代码能够正常运行,一定是需要自己做classLoader来加载.
运行流程
自定义类加载器的常规处理,1指定资源,2指定委托关系,3指定线程上下文类加载器,4调用逻辑入口方法.
1.指定资源
1.1 构造方法中,基于jar包的文件系统信息,构造Archive对象
public ExecutableArchiveLauncher() {
this.archive = createArchive();
}
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
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));
}
1.2 采集jar包中的classes 和lib目录下的归档文件.后边创建ClassLoader的时候作为参数传入
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
return archives;
}
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
2创建ClassLoader
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
//创建类加载器, 并指定归档文件
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
//创建类加载器, 将归档文件转换为URL
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
//父加载器是AppClassLoader
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
//getClass().getClassLoader() 是系统类加载器,因为默认情况下main方法所在类是由SystemClassLoader加载的,默认情况下是AppClassLoader.
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
3,4 设置线程上下文类加载器,调用逻辑入口方法
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(classLoader);
//调用MANIFEST.MF 中配置的Start-Class: xxx的main方法,还带入了参数
createMainMethodRunner(mainClass, args, classLoader).run();
}