Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >springboot的jar为何能独立运行

springboot的jar为何能独立运行

原创
作者头像
程序员欣宸
修改于 2021-05-13 07:28:24
修改于 2021-05-13 07:28:24
7700
举报
文章被收录于专栏:实战docker实战docker

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

能独立运行的jar文件

在开发springboot应用时,通过java -jar命令启动应用是常用的方式,今天就来一起了解这个简单操作背后的技术;

开发demo

开发一个springboot应用作为本次研究的对象,对应的版本信息如下:

  • JDK:1.8.0_211
  • springboot:2.3.1.RELEASE
  • maven:3.6.0

接下来开发springboot应用,这个应用非常简单:

  • springboot应用名为springbootstarterdemo,pom.xml文件内容:
代码语言:txt
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bolingcavalry</groupId>
    <artifactId>springbootstarterdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootstarterdemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 只有一个java类,里面有个http接口:
代码语言:txt
AI代码解释
复制
package com.bolingcavalry.springbootstarterdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@SpringBootApplication
@RestController
public class SpringbootstarterdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootstarterdemoApplication.class, args);
    }
    @RequestMapping(value = "/hello")
    public String hello(){
        return "hello " + new Date();
    }
}
  • 编码完成,在pom.xml所在目录执行命令
代码语言:txt
AI代码解释
复制
mvn clean package -U -DskipTests
  • 构建成功后,在target目录下得到文件springbootstarterdemo-0.0.1-SNAPSHOT.jar
  • 就是这个springbootstarterdemo-0.0.1-SNAPSHOT.jar,此时执行java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar就能启动应用,如下图:
在这里插入图片描述
在这里插入图片描述
  • 接下来就用这个springbootstarterdemo-0.0.1-SNAPSHOT.jar来分析jar文件能够独立启动的原因;

java -jar做了什么

  • 先要弄清楚java -jar命令做了什么,在官网找到了该命令的描述:

If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

  • 再次秀出我蹩脚的英文翻译:
  • 使用-jar参数时,后面的参数是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  • 该jar文件中包含的是class和资源文件;
  • 在manifest文件中有Main-Class的定义;
  • Main-Class的源码中指定了整个应用的启动类;(in its source code)

小结一下:

java -jar会去找jar中的manifest文件,在那里面找到真正的启动类;

探查springbootstarterdemo-0.0.1-SNAPSHOT.jar

  • springbootstarterdemo-0.0.1-SNAPSHOT.jar是前面的springboot工程的构建结果,是个压缩包,用常见的压缩工具就能解压,我这里的环境是MacBook Pro,用unzip即可解压;
  • 解压后有很多内容,我们先关注manifest相关的,下图红框中就是manifest文件:
    在这里插入图片描述
    在这里插入图片描述
  • 打开上图红框中的文件,内容如下:
代码语言:txt
AI代码解释
复制
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springbootstarterdemo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.1.RELEASE
Created-By: Maven Jar Plugin 3.2.0
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
  • 在上述内容可见Main-Class的值org.springframework.boot.loader.JarLauncher,这个和前面的java官方文档对应上了,正是这个JarLauncher类的代码中指定了真正的启动类;

疑惑出现

  • 在MANIFEST.MF文件中有这么一行内容:
代码语言:txt
AI代码解释
复制
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
  • 前面的java官方文档中,只提到过Main-Class ,并没有提到Start-Class
  • Start-Class的值是SpringbootstarterdemoApplication,这是我们的java代码中的唯一类,也只真正的应用启动类;
  • 所以问题就来了:理论上看,执行java -jar命令时JarLauncher类会被执行,但实际上是SpringbootstarterdemoApplication被执行了,这其中发生了什么呢?

猜测

动手之前先猜一下,个人觉得原因应该如下:

  1. java -jar命令会启动JarLauncher;
  2. Start-Class是给JarLauncher用的;
  3. JarLauncher根据Start-Class找到了SpringbootstarterdemoApplication,然后执行它;

分析JarLauncher

  • 先下载SpringBoot源码,我下载的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE
  • JarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的继承关系,如下图,可见JarLauncher继承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父类Launcher位于最顶层,是个抽象类:
    在这里插入图片描述
    在这里插入图片描述
  • java -jar执行的是JarLauncher的main方法,如下,会实例化一个JarLauncher对象,然后执行其launch方法,并且将所有入参都带入:
代码语言:txt
AI代码解释
复制
public static void main(String[] args) throws Exception {
	new JarLauncher().launch(args);
}
  • 上面的launch方法在父类Launcher中:
代码语言:txt
AI代码解释
复制
protected void launch(String[] args) throws Exception {
    // 将jar解压后运行的方式叫做exploded mode
    // 如果是exploded mode,就不能支持通过URL加载jar
    // 如果不是exploded mode,就可以通过URL加载jar
	if (!isExploded()) {
	    // 如果允许通过URL加载jar,就在此注册对应的处理类
		JarFile.registerUrlProtocolHandler();
	}
	// 创建classLoader
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	// jarmode是创建docker镜像时用到的参数,使用该参数是为了生成带有多个layer信息的镜像
	// 这里暂时不关注jarmode
	String jarMode = System.getProperty("jarmode");
	//如果没有jarmode参数,launchClass的值就来自getMainClass()返回
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);
}
  • 可见要重点关注的是getMainClass()方法,在看这个方法之前,我们先去关注一个重要的成员变量archive,是JarLauncher的父类ExecutableArchiveLauncher的archive,如下可见,该变量又来自方法createArchive
代码语言:txt
AI代码解释
复制
public ExecutableArchiveLauncher() {
		try {
			this.archive = createArchive();
			this.classPathIndex = getClassPathIndex(this.archive);
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
  • 方法来自Launcher.createArchive,如下所示,可见成员变量archive实际上是个JarFileArchive对象:
代码语言:txt
AI代码解释
复制
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));
	}
  • 现在回到getMainClass()方法,可见this.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的内容,然后getValue(START_CLASS_ATTRIBUTE)方法实际上就是从META-INF/MANIFEST.MF中取得了Start-Class的属性:
代码语言:txt
AI代码解释
复制
@Override
	protected String getMainClass() throws Exception {
	    // 对应的是JarFileArchive.getManifest方法,
	    // 进去后发现对应的就是JarFile.getManifest方法,
	    // JarFile.getManifest对应的就是META-INF/MANIFEST.MF文件的内容
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
		    // 对应的是META-INF/MANIFEST.MF文件中的Start-Class的属性
			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
  • 从上述分析可知:getMainClass()方法返回的是META-INF/MANIFEST.MF中取得了Start-Class的属性com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication,再次回到launch方法中,可见最终运行的代码是launch(args, launchClass, classLoader),它的launchClass参数就是com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:
代码语言:txt
AI代码解释
复制
protected void launch(String[] args) throws Exception {
		if (!isExploded()) {
			JarFile.registerUrlProtocolHandler();
		}
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		String jarMode = System.getProperty("jarmode");
		// 这里的launchClass等于"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
		// 这里就是启动SpringbootstarterdemoApplication的地方
		launch(args, launchClass, classLoader);
	}
  • 展开launch(args, launchClass, classLoader),最终查到了MainMethodRunner类:
代码语言:txt
AI代码解释
复制
public class MainMethodRunner {

	private final String mainClassName;

	private final String[] args;

	/**
	 * Create a new {@link MainMethodRunner} instance.
	 * @param mainClass the main class
	 * @param args incoming arguments
	 */
	public MainMethodRunner(String mainClass, String[] args) {
	    // mainClassName被赋值为"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		this.mainClassName = mainClass;
		this.args = (args != null) ? args.clone() : null;
	}

	public void run() throws Exception {
	    // 得到SpringbootstarterdemoApplication的Class对象
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		// 得到SpringbootstarterdemoApplication的main方法对象
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.setAccessible(true);
		// 通过反射执行main方法
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

终于,真相大白了;

小结

最后尽可能简短做个小结,先看jar是如何产生的,如下图,maven插件生成的jar文件中,有常见的class、jar,也有符合java规范的MANIFEST.MF文件,并且,还在MANIFEST.MF文件中额外生成了名为Start-Class的配置,这里面是我们编写的应用启动类SpringbootstarterdemoApplication

在这里插入图片描述
在这里插入图片描述

启动类是JarLauncher,它是如何与MANIFEST.MF文件关联的呢?从下图可以看出,最终是通过JarFile类的成员变量manifestSupplier关联上的:

在这里插入图片描述
在这里插入图片描述

再来看看关键代码的执行情况,如下图:

在这里插入图片描述
在这里插入图片描述

至此,SpringBoot的jar独立运行的基本原理已经清楚,探究的过程中,除了熟悉关键代码流程,还对jar中的文件有了更多了解,如果您正在学习SpringBoot,希望本文能给您一些参考;

官方文档

  1. 最后附上SpringBoot官方文档,可以看到Start-Class描述信息:
    在这里插入图片描述
    在这里插入图片描述
  2. 上述文档明确提到:Start-Class定义的是实际的启动类,此时的您应该对一切都了然于胸,产生本该如此的感慨;

关于容器和镜像的环境

如果您不想自己搭建kubernetes环境,推荐使用腾讯云容器服务TKE:无需自建,即可在腾讯云上使用稳定, 安全,高效,灵活扩展的 Kubernetes 容器平台;

如果您希望自己的镜像可以通过外网上传和下载,推荐腾讯云容器镜像服务TCR:像数据加密存储,大镜像多节点快速分发,跨地域镜像同步

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
你的springboot starter是如何生效的?
一、背景描述二、自定义starter实现方式三、自定义starter依赖加载原理四、spring.factories加载与解析五、参考
叔牙
2023/08/09
6090
你的springboot starter是如何生效的?
SpringBoot打包部署解析:Launcher实现原理
Launcher实现原理 在上节内容中,我们得知 jar 包 Main-Class 指定入口程序为 Spring Boot 提供的 L auncher(JarL auncher),并不是我们在 Sp
愿天堂没有BUG
2022/10/28
1K0
SpringBoot打包部署解析:Launcher实现原理
SpringBoot项目jar、war包启动解析
war包启动:需要先启动外部的Web服务器,实现Servlet3.0规范中引导应用启动类,然后将war包放入Web服务器下,Web服务器通过回调引导应用启动类方法启动应用。
阿珍
2023/03/20
2K0
当我们的执行 java -jar xxx.jar 的时候底层到底做了什么?
大家都知道我们常用的 SpringBoot 项目最终在线上运行的时候都是通过启动 java -jar xxx.jar 命令来运行的。
Java极客技术
2023/02/23
7680
当我们的执行 java -jar xxx.jar 的时候底层到底做了什么?
SpringBoot 核心模块原理剖析
微服务始终一个相对热门的话题,SpringBoot 则以其轻量级、内嵌 Web 容器、一键启动、方便调试等特点被越来越多的微服务实践者所采用。 知其然还要知其所以然,你了解 SpringBoot 中三大核心模块的实现原理吗? 三大核心模块 spring-boot-load 模块 正常情况下一个类加载器只能找到加载路径的 jar 包里当前目录或者文件类里面的 *.class 文件,SpringBoot 允许我们使用 java -jar archive.jar 运行包含嵌套依赖 jar 的 jar 或者 war
CSDN技术头条
2018/04/18
1.5K0
硬核艿艿,新鲜出炉,直接带你弄懂 Spring Boot Jar 启动原理!
Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin,可以方便的将 Spring Boot 项目打成 jar 包或者 war 包。
芋道源码
2020/06/05
2K0
Spring boot 项目打出来的包启动过程
spring boot 的工程支持打包为jar和war,打包成 jar 或 war 可以直接用 java -jar xxx.jar 来启动,war包也可以放入tomcat等容器中运行。
前Thoughtworks-杨焱
2021/12/07
1.7K0
Spring boot 项目打出来的包启动过程
SpringBoot - 探究Spring Boot应用是如何通过java -jar 启动的
大家开发的基于Spring Boot 的应用 ,jar形式, 发布的时候,绝大部分都是使用java -jar 启动。 得益于Spring Boot 的封装 , 再也不用操心搭建tomcat等相关web容器le , 一切变得非常美好, 那SpringBoot是怎么做到的呢?
小小工匠
2021/08/17
1.5K0
SpringBoot - 探究Spring Boot应用是如何通过java -jar 启动的
Springboot模块分类
springboot可以依据classpath里面的依赖的内容来自动配置bean到IOC容器,但是要开启这个自动配置功能需要添加@EnableAutoConfiguration注解。Auto-configuration会尝试推断哪些beans是用户可能会需要的。比如如果HSQLDB包在当前classpath下,并且用户并没有配置其他数据库链接,这时候Auto-configuration功能会自动注入一个基于内存的数据库连接到应用的IOC容器。
加多
2018/09/06
4270
为什么SpringBoot的 jar 可以直接运行?
SpringBoot提供了一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。在pom文件里加入这个插件即可:
JAVA葵花宝典
2020/04/23
1.2K0
Spring Boot 核心编程思想-第一部分-读书笔记「建议收藏」
本文是Spring Boot核心编程思想记录的笔记,书籍地址:Spring Boot编程思想(核心篇):
全栈程序员站长
2022/07/20
9580
Spring Boot 核心编程思想-第一部分-读书笔记「建议收藏」
SpringBoot打包部署解析:jar包的生成和结构
SpringBoot项目支持两种常见的打包形式: jar 包和 war 包。默认情况下创建的 Spring Boot项目是采用 jar 包形式,如果项目需要 war 包,可通过修改配置打成 war 包。
愿天堂没有BUG
2022/10/28
1.8K0
SpringBoot打包部署解析:jar包的生成和结构
SpringBoot从0到实战5:项目是如何通过jar包启动的?
仔细观察发现Start-Class指向的是我们的启动类HelloStsApplication,也就是注解了@SpringBootApplication的类。而Main-Class则可以理解为真正的启动类。
程序员洲洲
2024/06/06
1620
SpringBoot从0到实战5:项目是如何通过jar包启动的?
spring boot应用启动原理分析
摘要: spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server。
Java架构师历程
2018/09/26
1.9K0
SpringBoot系列-- jar可执行原理
文章篇幅较长,但是包含了SpringBoot 可执行jar包从头到尾的原理,请读者耐心观看。
田维常
2020/02/13
9420
Java类加载器机制及应用
例: 在main方法中, new了一个 Test 类, 那么这个Test类在new动作发生时, 才会被ClassLoader加载. 若 Test 类被两个不同的类加载器加载了, 那么对应的实例Class是完全不同的, JVM只保证同一个加载器内不会有重复的类
code-x
2022/08/08
3670
SpringBoot 系列-FatJar 启动原理
小伙伴们,大家好!从今天开始,我将持续更新磊叔的SpringBoot 系列文章,还请大家多多关注,多多转发,为我们开发者圈多分享些干货,在此谢过各位了!
安徽开发者圈
2020/03/02
1.8K0
MANIFEST.MF是个什么?
写这篇文件主要记录JRA文件里面到底是什么?然后MANIFEST.MF又是什么?Springboot 如何只有Main方法就可以运行的?
全栈程序员站长
2022/09/07
7960
MANIFEST.MF是个什么?
SpringBoot的启动引导类真的是XXApplication吗?
SpringBoot项目中的启动类,一般都是XXApplication,例如StatsApplication,UnionApplication。每个项目的启动类名称都不一样。但是它的启动类真的是XXApplication吗?
温安适
2020/06/07
1.3K0
Spring Boot Maven Plugin打包异常及三种解决方法:Unable to find main class
【背景】spring-boot项目,打包成可执行jar,项目内有两个带有main方法的类并且都使用了@SpringBootApplication注解(或者另一种情形:你有两个main方法并且所在类都没有使用@SpringBootApplication注解),pom.xml如下 <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <v
庞小明
2018/07/06
15.9K0
推荐阅读
相关推荐
你的springboot starter是如何生效的?
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文