【七】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);

}

}

相关推荐
北冥you鱼18 分钟前
Go 语言读取链上数据:从基础到实战
开发语言·后端·golang
程序员cxuan36 分钟前
Claude Code 为了封禁中国用户,竟然在代码里下毒
人工智能·后端·程序员
郡杰43 分钟前
Git基础与开发平台搭建
git·后端
ServBay1 小时前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好1 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780512 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python
Java面试题总结2 小时前
CubeSandbox 实战:从零部署到快照/克隆/回滚全体验
后端
万亿少女的梦1682 小时前
基于SpringBoot的考研学习交流系统设计与开发
spring boot·mysql·vue·系统设计·论坛系统
Csvn2 小时前
Python 开发技巧:函数式编程与 lambda 进阶
后端
考虑考虑2 小时前
nginx配置ssl
运维·后端·nginx