【七】SpringBoot为什么可以打成 jar包启动

SpringBoot为什么可以打成 jar包启动

**简介:**庆幸的是夜跑的习惯一直都在坚持,正如现在坚持写博客一样。最开始刚接触springboot的时候就觉得很神奇,当时也去研究了一番,今晚夜跑又想起来了这茬事,于是想着应该可以记录一下了,不至于下次想不来了又去翻资料。

一、SpringBoot生成的jar包是什么

Spring Boot的可执行jar包又称作"fat jar",那什么是fat jar呢?在java中,将应用程序及其依赖jar一起打包到一个独立的jar中,就叫fat jar,它也叫uberJar。springboot的打包方式就是这样,将应用程序代码打包到BOOT-INF.classes,将依赖包打包到BOOT-INF.lib目录,这里我们以xxl-job-admin-2.4.0-SNAPSHOT.jar为例来做说明,我们使用反编译工具jd将jar打开,目录如下:

各目录存放内容如下:

BOOT-INF/classes:目录存放应用编译后的class文件。

BOOT-INF/lib:目录存放应用依赖的第三方JAR包文件。

META-INF:目录存放应用打包信息(Maven坐标、pom文件)和MANIFEST.MF文件。

org:目录存放SpringBoot相关class文件。

这里我们首先关注一下配置文件:MANIFEST.MF,内容如下:

Manifest-Version: 1.0

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx

Archiver-Version: Plexus Archiver

Built-By: user

Spring-Boot-Layers-Index: BOOT-INF/layers.idx

Start-Class: com.xxl.job.admin.XxlJobAdminApplication

Spring-Boot-Classes: BOOT-INF/classes/

Spring-Boot-Lib: BOOT-INF/lib/

Spring-Boot-Version: 2.6.7

Created-By: Apache Maven 3.6.1

Build-Jdk: 1.8.0_211

Main-Class: org.springframework.boot.loader.JarLauncher

参考 Oracle 官方对该的说明:

Main-Class:Java 规定的 jar 包的启动类,这里设置为 spring-boot-loader 项目的 JarLauncher 类,进行 Spring Boot 应用的启动

Start-Class:Spring Boot 规定的主启动类,这里通过 Spring Boot Maven Plugin 插件打包时,会设置为我们定义的 Application 启动类

为什么不直接将我们的 Application 启动类设置为 Main-Class 启动呢?

因为通过 Spring Boot Maven Plugin 插件打包后的 jar 包,我们的 .class 文件在 BOOT-INF/classes/ 目录下,在 Java 默认的 jar 包加载规则下找不到我们的 Application 启动类,也就需要通过 JarLauncher 启动加载。当然,还有一个原因,Java 规定可执行器的 jar 包禁止嵌套其它 jar 包,在 BOOT-INF/lib 目录下有我们 Spring Boot 应用依赖的所有第三方 jar 包,因此spring-boot-loader 项目自定义实现了 ClassLoader 实现类 LaunchedURLClassLoader,支持加载 BOOT-INF/classes 目录下的 .class 文件,以及 BOOT-INF/lib 目录下的 jar 包。

二、JarLauncher启动器实现原理

上文描述了Application 的Main-Clas启动类是JarLauncher 类,那么接下来我们一起来看看 Spring Boot 的 JarLauncher 这个类

JarLauncher的继承关系如下:

JarLauncher全路径是org.springframework.boot.loader.JarLauncher

public class JarLauncher extends ExecutableArchiveLauncher {

private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";

static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {

if (entry.isDirectory()) {

return entry.getName().equals("BOOT-INF/classes/");

}

return entry.getName().startsWith("BOOT-INF/lib/");

};

public JarLauncher() {

}

protected JarLauncher(Archive archive) {

super(archive);

}

@Override

protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {

// Only needed for exploded archives, regular ones already have a defined order

if (archive instanceof ExplodedArchive) {

String location = getClassPathIndexFileLocation(archive);

return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);

}

return super.getClassPathIndex(archive);

}

private String getClassPathIndexFileLocation(Archive archive) throws IOException {

Manifest manifest = archive.getManifest();

Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;

String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null;

return (location != null) ? location : DEFAULT_CLASSPATH_INDEX_LOCATION;

}

@Override

protected boolean isPostProcessingClassPathArchives() {

return false;

}

@Override

protected boolean isSearchCandidate(Archive.Entry entry) {

return entry.getName().startsWith("BOOT-INF/");

}

@Override

protected boolean isNestedArchive(Archive.Entry entry) {

return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);

}

public static void main(String\[\] args) throws Exception {

new JarLauncher().launch(args);

}

}

通过new一个JarLauncher().launch(args)方式进行启动。

public class Jarlauncher{

...

private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";

...

protected void launch(String\[\] args) throws Exception {

//判断是否以一个分解模式的方式运行,如果是则运行,否则只支持规范的jar文件从而选择跳过

if (!isExploded()) {

JarFile.registerUrlProtocolHandler();

}

//获取类加载器

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

//获取系统中的jar模型

String jarMode = System.getProperty("jarmode");

//加载启动引导类

String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER :

getMainClass();

//启动应用

launch(args, launchClass, classLoader);

}

}

相关推荐
普通网友8 小时前
Springboot中使用Elasticsearch(部署+使用+讲解 最完整)
spring boot·elasticsearch·jenkins
笃行3508 小时前
向量数据库单独建一套,这笔账划不划算
后端
weixin_431600449 小时前
Agent Workflow 学习向:Code 节点,比模板更强的变量加工
后端·python·学习·ai·
Rain的Java大神之路9 小时前
短信接口被狂刷怎么处理
java·运维·后端·web安全·面试·架构·xss
孙克旭_9 小时前
Docker 镜像构建|Vue+SpringBoot+Go 多语言项目 Dockerfile 案例
linux·运维·vue.js·spring boot·docker·dockerfile
2602_9599609210 小时前
谢飞机面试大厂:Spring Boot、JVM、Redis、Kafka、微服务与音视频搜索场景求生实录
java·jvm·spring boot·redis·面试题
Lost of 程序猿10 小时前
AOP 实战:面向切面编程从理论到落地
后端·asp.net·aop·面向切片变成
Lost of 程序猿10 小时前
ASP.NET Core 认证授权实战:从 JWT 到 Policy,设计一套企业级 RBAC 权限系统
后端·asp.net
Lost of 程序猿10 小时前
ASP.NET Core 后台任务全景:从 BackgroundService 到 Channel 队列,再到分布式调度
后端·asp.net·.netcore
倾颜11 小时前
NestJS 核心概念梳理:从 Module、Controller 到 Guard、Interceptor
后端·node.js·nestjs