首页 > 其他分享 >SpringBoot应用jar包启动原理详解

SpringBoot应用jar包启动原理详解

时间:2023-10-17 16:15:39浏览次数:48  
标签:JAR SpringBoot jar BOOT 详解 JarLauncher INF Class

1、maven打包

Spring Boot项目的pom.xml文件中默认使用spring-boot-maven-plugin插件进行打包:

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
  </plugin>
  </plugins>
</build>

在执行完maven clean package之后,会生成两个jar相关文件:

  • test-0.0.1-SNAPSHOT.jar
  • test-0.0.1-SNAPSHOT.jar.original

2、jar包目录结构

以笔者的test-0.0.1-SNAPSHOT.jar为例,来看一下jar的目录结构,其中都包含哪些目录和文件?

可以概述为:

其中主要包括三大目录:META-INF、BOOT-INF、org。

2.1 META-INF内容

META-INF记录了相关jar包的基础信息,包括:入口程序。具体内容如下:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: tms-start
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.saint.StartApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.5
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

Main-Class是org.springframework.boot.loader.JarLauncher,即jar启动的Main函数;
Start-Class是com.saint.StartApplication,即我们自己SpringBoot项目的启动类;也是下文提到的项目的引导类。

2.2 BOOT-INF内容

  • BOOT-INF/classes目录:存放应用编译后的class文件源码;
  • BOOT-INF/lib目录:存放应用依赖的所有三方jar包文件;

2.3 org目录内容

org目录下存放着所有SpringBoot相关的class文件,比如:JarLauncher、LaunchedURLClassLoader。

3、可执行Jar(JarLauncher)

从jar包内META-INF/MANIFEST.MF文件中的Main-Class属性值为org.springframework.boot.loader.JarLauncher,可以看出main函数是JarLauncher,即:SpringBoot应用中的Main-class属性指向的class为org.springframework.boot.loader.JarLauncher。
其实吧,主要是 Java官方文档规定:java -jar命令引导的具体启动类必须配置在MANIFEST.MF资源的Main-class属性中;又根据“JAR文件规范”,MANIFEST.MF资源必须存放在/META-INF/目录下。所以main函数才是JarLauncher。
JarLauncher类继承图如下:

从JarLauncher的类注释我们看出JarLauncher的作用:

  • 加载内部/BOOT-INF/lib下的所有三方依赖jar;
  • 加载内部/BOOT-INF/classes下的所有应用class;

3.1 JarLauncher的运行步骤?

  • 在解压jar包后的根目录下运行 java org.springframework.boot.loader.JarLauncher。项目引导类(META-INF/MANIFEST.MF文件中的Start-Class属性)被JarLauncher加载并执行。
  • 如果直接运行Start-Class(示例的StartApplication)类,会报错ClassNotFoundException。
  • Spring Boot依赖的jar文件均存放在BOOT-INF/lib目录下。JarLauncher会将这些jar文件作为Start-Class的类库依赖。

这也是为什么JarLauncher能够引导,而直接运行Start-Class却不行。

3.2 JarLauncher实现原理?

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	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);
	}

}

JarLauncher#main()中新建了JarLauncher并调用父类Launcher中的launch()方法启动程序;

  • BOOT_INF_CLASSES、BOOT_INF_LIB变量对应BOOT-INF/classes和lib路径;
  • isNestedArchive(Archinve.Entry entry)方法用于判断FAT JAR资源的相对路径是否为nestedArchive嵌套文档。进而决定这些FAT JAR是否会被launch。 当方法返回false时,说明FAT JAR被解压至文件目录。

Archive的概念
archive即归档文件,这个概念在linux下比较常见;通常就是一个tar/zip格式的压缩包;而jar正是zip格式的。
SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),也可以是文件目录(ExplodedArchive);这样也就统一了访问资源的逻辑层;

public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
    ....
}

Archive继承自Archive.Entry,Archive.Entry有两种实现:

  • JarFileArchive.JarFileEntry --> 基于java.util.jar.JarEntry实现,表示FAT JAR嵌入资源。
  • ExplodedArchive.FileEntry --> 基于文件系统实现;

两者的主要差别是ExplodedArchive相比于JarFileArchive多了一个获取文件的getFile()方法;

public File getFile() {
    return this.file;
}

也就是说一个在jar包环境下寻找资源,一个在文件夹目录下寻找资源;
所以从实现层面证明了JarLauncher支持JAR和文件系统两种启动方式
当执行java -jar命令时,将调用/META-INF /MANIFEST.MF文件的Main-Class属性的main()方法,实际上调用的是JarLauncher#launch(args)方法;

3.3 Launcher#launch(args)方法

protected void launch(String[] args) throws Exception {
    if (!isExploded()) {
        // phase1:注册jar URL处理器
        JarFile.registerUrlProtocolHandler();
    }
    // phase2:创建ClassLoader
    ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
    String jarMode = System.getProperty("jarmode");
    String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
    // phase3:调用实际的引导类launch
    launch(args, launchClass, classLoader);
}

launch()方法分三步:

  • 注册jar URL处理器;
  • 为所有的Archive创建可以加载jar in jar目录的ClassLoader;
  • 调用实际的引导类(Start-Class);

3.3.1 phase1 注册jar URL处理器

private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";

	private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";

public static void registerUrlProtocolHandler() {
    String handlers = System.getProperty(PROTOCOL_HANDLER, "");
    System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
                                          : handlers + "|" + HANDLERS_PACKAGE));
    // 重置缓存的UrlHandlers;
    resetCachedUrlHandlers();
}

private static void resetCachedUrlHandlers() {
    try {
        // 由URL类实现:通过URL.setURLStreamHandlerFactory()获得URLStreamHandler。
        URL.setURLStreamHandlerFactory(null);
    }
    catch (Error ex) {
        // Ignore
    }
}

JarFile#resetCachedUrlHandlers()方法利用java.net.URLStreamHandler扩展机制,实现由URL#getURLStreamHandler(String)提供。
URL#getURLStreamHandler(String protocol)方法:
首先,URL的关联协议(Protocol)对应一种URLStreamHandler实现类。
JDK内建了一些协议的实现,这些实现均存放在sun.net.www.protocol包下,并且类名必须为Handler,其类全名模式为sun.net.www.protocol.${protocol}.Handler(包名前缀.协议名.Handler),其中${protocol}表示协议名。
如果需要扩展,则必须继承URLStreamHandler类,通过配置Java系统属性java.protocol.handler.pkgs,追加URLStreamHandler实现类的package,多个package以“|”分割。
所以对于SpringBoot的JarFile,registerURLProtocolHandler()方法将package org.springframework.boot.loader追加到java系统属性java.protocol.handler.pkgs中。
也就是说,org.springframework.boot.loader包下存在协议对应的Handler类,即org.springframework.boot.loader.jar.Handler;并且按照类名模式,其实现协议为JAR。
另外:在URL#getURLStreamHandler()方法中,处理器先读取Java系统属性java.protocol.handler.pkgs,无论其是否存在,继续读取sun.net.www.protocol包;所以JDK内建URLStreamHandler实现是兜底的
为什么SpringBoot要选择覆盖URLStreamHandler?

  • Spring BOOT FAT JAR除包含传统Java Jar资源之外,还包含依赖的JAR文件;即存在jar in jar的情况;
  • 默认情况下,JDK提供的ClassLoader只能识别jar中的class文件以及加载classpath下的其他jar包中的class文件,对于jar in jar的包无法加载;
  • 当SpringBoot FAT JAR被java -jar命令引导时,其内部的JAR文件无法被内嵌实现sun.net.www.protocol.jar.Handler当做class Path,故需要定义了一套URLStreamHandler实现类和JarURLConnection实现类,用来加载jar in jar包的class类文件;

3.3.2 phase2 创建可以加载jar in jar目录的ClassLoader

获取所有的Archive,然后针对每个Archive分别创建ClassLoader;

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

/**
 * 获取所有的Archive(包含jar in jar的情况)
 */
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
    return getClassPathArchives().iterator();
}

/**
 * 针对每个Archive分别创建ClassLoader
 */
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]));
}

3.3.3 phase3 调用实际的引导类(Start-Class)

// case1: 通过ExecutableArchiveLauncher#getMainClass()获取MainClass
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
// 2、运行实际的引导类
launch(args, launchClass, classLoader);

对于phase3,大致可以分为两步:

  • 首先通过ExecutableArchiveLauncher#getMainClass()获取mainClass(即:/META-INF/MANIFEST.MF资源中的Start-Class属性);
  • 利用反射获取mainClass类中的main(Stirng[])方法并调用;

<1> 获取mainClass:

Start-Class属性来自/META_INF/MANIFEST.MF资源中。Launcher的子类JarLauncher或WarLauncher没有实现getMainClass()方法。所以无论是Jar还是War,读取的SpringBoot启动类均来自此属性。
<2> 执行mainClass的main()方法:
获取mainClass之后,MainMethodRunner#run()方法利用反射获取mainClass类中的main(Stirng[])方法并调用。

运行JarLauncher实际上是在同进程、同线程内调用Start-Class类的main(Stirng[])方法,并且在调用前准备好Class Path。

4、WarLauncher

WarLauncher是可执行WAR的启动器。
WarLauncher与JarLauncher的差异很小,主要区别在于项目文件和JAR Class Path路径的不同。

  • 相比于FAT Jar的目录,WAR增加了WEB-INF/lib-provided,并且该目录仅存放provided的JAR文件。
  • 传统的Servlet应用的Class Path路径仅关注WEB-INF/classes/和WEB-INF/lib/目录,因此WEB-INF/lib-provided/中的JAR将被Servlet忽略

好处:打包后的WAR文件能够在Servlet容器中兼容运行
所以JarLauncher和WarLauncher并无本质区别。

5、总结

Spring Boot应用Jar/War的启动流程:
Spring Boot应用打包之后,生成一个Fat jar,包含了应用依赖的所有三方jar包和SpringBoot Loader相关的类。
Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载BOOT-INF/classes目录以及/BOOT-INF/lib下面的jar,并利用反射获取mainClass类中的main(Stirng[])方法并调用。即:运行JarLauncher实际上是在同进程、同线程内调用Start-Class类的main(Stirng[])方法,并且在调用前准备好Class Path。
其他点:
SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。
SpringBoot通过扩展URLClassLoader --> LauncherURLClassLoader,实现了jar in jar中class文件的加载。
WarLauncher相比JarLauncher只是多加载WEB-INF/lib-provided目录下的jar文件。

来源:https://blog.csdn.net/Saintmm/article/details/123790998

标签:JAR,SpringBoot,jar,BOOT,详解,JarLauncher,INF,Class
From: https://www.cnblogs.com/star95/p/17769926.html

相关文章

  • 软件开发流程详解
    转从网络软件是怎么开发出来的?怎么进行软件开发流程详解一款软件是如何从零到开发出来的,需要经过哪些流程?需要哪些人员参与?开发软件的过程非常漫长,技术门槛高,花费资金多。在移动互联网时代,这里就以app软件开发为例,为大家详细介绍APP软件开发流程:开发一款软件,不仅仅是一个单......
  • springboot启动流程源码解析(带流程图)
    大致流程如下:初始化SpringApplication,从META-INF下的spring.factories读取ApplicationListener/ApplicationContextInitializer运行SpringApplication的run方法读取项目中环境变量、jvm配置信息、配置文件信息等创建Spring容器对象(ApplicationContext)利用ApplicationCon......
  • javaWeb-MVC 和三层架构(非详解)
    1.MVC模式MVC结构:MVC是一种分层开发的模式其中1.M:Model,业务模型,处理业务2.V:View,视图,界面展示3.C:Controller,控制器,处理请求,调用模型和视图 下面是一张展示流程控制图MVC的好处:1.职责单一,互不影响2.有利于分工协作3.利于维护,利于组件重用2.三层架构1表现层2.业务逻辑......
  • iOS代码混淆和加固技术详解
    目录 摘要:本文介绍了iOS开发中常用的代码混淆和加固技术,包括数据加密、应用加壳和代码混淆。其中,重点讨论了代码混淆的实现方法和注意事项,并推荐了一些相关的工具和库。引言代码混淆和加固数据加密应用加壳代码混淆总结参考资料  摘要:本文介绍了iOS开发中常用的......
  • 2023香山杯nesting详解
    nesting通过函数分析,有一个VM的指令解析器,也看不懂,VM的题看起来特别费劲在sub_16BC里面找cmp的flag比对指令,0x1E21和0x1EC9。最终发现输入正确的字符和错误的字符,0x1E21处的指令执行次数不一样,可以通过输入fo,fl,fi,其中fl是正确的字符,发现正确的字符在0x1E21处执行的次数更多,因......
  • php中关于token验证的相关问题详解
    这篇文章主要介绍了php中关于token验证的相关问题详解的相关资料,需要的朋友可以参考下目录token验证首先是为什么用?那么怎么用呢?token的组成一般token组成:JWT组成:标准的组成:总结token验证什么是token?我相信很多开发者都或多或少听过基于token的用户鉴权和基于session的用户......
  • springboot heapdump信息获取
    springboot信息泄露可能泄漏的路由/api-docs/v2/api-docs/swagger-ui.html/api.html/sw/swagger-ui.html/api/swagger-ui.html/template/swagger-ui.html/spring-security-rest/api/swagger-ui.html/spring-security-oauth-resource/swagger-ui.html/mappings/actua......
  • Qt OpenGL textures详解
    1.初始化opengl资源 Q_INIT_RESOURCE:textures(资源名称)QSurfaceFormat:定义3d面显示方式如果在vs+qtvstools中无法正常显示3d图形,则需加入以下代码:format.setStenciBufferSize(8);format.setVersion(1,1);版本号根据用户安装的Qt版本来更改2.继承QOpenGLWidget和QOpen......
  • JDBC API详解
    //DriverManage//1.注册驱动//2.获取数据库连接//Connection//1.获取执行SQL的对象StatementcreateStatement();//普通执行sql对象PrepareStatementprepareStatement(sql);//预编译sql的执行sql对象:防止sql注入CallableStatementprepareCall(sql);//执行存储过程......
  • selenium之鼠标操作详解
    前言人类频繁的用手操作鼠标和键盘,为了解决这个问题,selenium工具为我们提供了一个类来处理这些事件—Actionchains,该类可以完成鼠标移动,鼠标点击事件、键盘输入、内容菜单交互等交互行为。1、常见的鼠标操作有:单击、右键点击、双击、移动鼠标、拖拽鼠标。(Actionchains代替人......