Spring Boot 애플리케이션은 spring-boot-maven-plugin
을 사용하여 빠르게 패키징하여 spring-boot-maven-plugin
快速打包,构建一个可执行jar。Spring Boot内嵌容器,通过java -jar
命令便可以直接启动应用。
虽然是一个简单的启动命令,背后却藏着很多知识。今天带着大家探索FAT JAR启动的背后原理。本文主要包含以下几个部分:
JAR 是什么。首先需要了解jar是什么,才知道java -jar
做了什么事情。
FatJar 有什么不同。 Spring Boot提供的可执行jar与普通的jar有什么区别。
启动时的类加载原理。 启动过程中类加载器做了什么?Spring Boot又如何通过自定义类加载器解决内嵌包的加载问题。
启动的整个流程。最后整合前面三部分的内容,解析源码看如何完成启动。
JAR文件(Java归档,英语: Java ARchive)是一种软件包文件格式,通常用于将大量的Java类文件、相关的元数据和资源(文本、图片等)文件聚合到一个文件,以便分发Java平台应用软件或库。简单点理解其实就是一个压缩包,既然是压缩包那么为了提取JAR文件的内容,可以使用任何标准的unzip解压缩软件提取内容。或者使用Java虚拟机自带命令jar -xf foo.jar
来解压相应的jar文件。
JAR 可以简单分为两类:
非可执行JAR。打包时,不用指定main-class
,也不可运行。普通jar包可以供其它项目进行依赖。
可执行JAR。打jar包时,指定了main-class
类,可以通过java -jar xxx.jar
命令,执行main-class
的main
方法,运行jar包。可运行jar包不可被其他项目进行依赖。
不管是非可行JAR还是可执行JAR解压后都包含两部分:META-INF
目录(元数据)和package
目录(编译后的class)。这种普通的jar不包含第三方依赖包,只包含应用自身的配置文件、class 等。
. ├── META-INF │ ├── MANIFEST.MF #定义 └── org # 包路径(存放编译后的class) └── springframework
JAR包的配置文件是META-INF
文件夹下的MANIFEST.MF
文件。主要配置信息如下:
Manifest-Version: 用来定义manifest文件的版本,例如:Manifest-Version: 1.0
Created-By: 声明该文件的生成者,一般该属性是由jar命令行工具生成的,例如:Created-By: Apache Ant 1.5.1
Signature-Version: 定义jar文件的签名版本
Class-Path: 应用程序或者类装载器使用该值来构建内部的类搜索路径,可执行jar包里需要设置这个。
上面是普通jar包的属性,可运行jar包的.MF文件中,还会有mian-class
或start-class
等属性。如果依赖了外部jar包,还会在MF文件中配置lib路径等信息。更多信息参见:maven为MANIFEST.MF文件添加内容的方法
至于可运行jar包和普通jar包的目录结构,没有什么特别固定的模式,总之,无论是什么结构,在.MF文件中,配置好jar包的信息,即可正常使用jar包了。
普通的jar只包含当前 jar的信息,不含有第三方 jar。当内部依赖第三方jar时,直接运行则会报错,这时候需要将第三方jar内嵌到可执行jar里。将一个jar及其依赖的三方jar全部打到一个包中,这个包即为 FatJar。
Spring Boot
为了解决内嵌jar问题,提供了一套FatJar解决方案,分别定义了jar目录结构和 MANIFEST.MF
。在编译生成可执行 jar 的基础上,使用spring-boot-maven-plugin
按Spring Boot 的可执行包标准repackage
실행 가능한 jar을 빌드할 수 있습니다. Spring Boot에는 컨테이너가 내장되어 있으며 java -jar
명령을 통해 애플리케이션을 직접 시작할 수 있습니다.
java -jar
가 무엇을 하는지 알기 전에 jar가 무엇인지 이해해야 합니다. 🎜jar -xf foo.jar
명령을 사용하여 해당 jar 파일의 압축을 해제하세요. 🎜🎜🎜JAR은 간단히 두 가지 범주로 나눌 수 있습니다: 🎜🎜main-class
를 지정할 필요가 없으며 실행할 수 없습니다. 일반 jar 패키지는 다른 프로젝트에서 의존하는 데 사용될 수 있습니다. 🎜main-class
클래스가 지정됩니다. java -jar xxx.jar
명령을 사용하여 main-class
를 실행할 수 있습니다. code> >main 메소드는 jar 패키지를 실행합니다. 실행 가능한 jar 패키지 🎜는 다른 프로젝트에 의존할 수 없습니다. 🎜META-INF
디렉터리(메타데이터) 및 package
디렉터리(컴파일된 클래스). 이 일반 jar에는 타사 종속성 패키지가 포함되지 않고 애플리케이션 자체 구성 파일, 클래스 등만 포함됩니다. 🎜<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>🎜설명 파일 MANIFEST.MF🎜🎜JAR 패키지 구성 파일은
META-INF
폴더 하위의 MANIFEST.MF
파일입니다. 🎜주요 구성 정보🎜는 다음과 같습니다. 🎜mian-class
또는 start도 있습니다. -class 및 기타 속성. 외부 jar 패키지를 사용하는 경우 lib 경로 및 기타 정보도 MF 파일에 구성됩니다. 🎜자세한 내용은🎜참조: maven이 MANIFEST.MF 파일에 콘텐츠를 추가하는 방법🎜🎜 🎜runnable jar 패키지🎜 및 🎜일반 jar 패키지🎜의 디렉터리 구조에는 특별히 고정된 패턴이 없습니다. 구조에 관계없이 .MF 파일에 jar 패키지 정보를 설정하면 jar 패키지를 정상적으로 사용할 수 있습니다. 🎜🎜FatJar의 차이점은 무엇인가요?🎜<h4>FatJar가 무엇인가요? </h4>🎜일반 jar에는 현재 jar에 대한 정보만 포함되며 타사 jar는 포함되지 않습니다. 내부적으로 타사 jar를 사용하는 경우 직접 실행하면 오류가 보고됩니다. 이 경우 타사 jar를 실행 가능한 jar에 포함해야 합니다. 🎜병과 이에 종속된 타사 항아리를 하나의 패키지에 담는 것이 FatJar입니다. 🎜🎜<h4>SpringBoot FatJar 솔루션</h4>🎜<code>Spring Boot
는 🎜jar 디렉터리 구조🎜 및 MANIFEST를 각각 정의하는 내장된 jar 문제를 해결하기 위한 FatJar 솔루션 세트를 제공합니다. .MF
. 실행 가능한 jar를 컴파일하고 생성한 후, Spring Boot의 실행 가능 패키지 표준 repackage
에 따라 spring-boot-maven-plugin
을 사용하여 실행 가능한 Spring Boot jar를 얻습니다. 🎜실행 가능한 Jar의 종류에 따라 실행 가능한 Jar와 실행 가능한 War의 두 가지 유형으로 나뉩니다. 🎜🎜🎜spring-boot-maven-plugin 패키징 과정🎜🎜새로 생성된 빈 SpringBoot 프로젝트 어디에도 관련 클래스에 대한 명시적인 소개나 작성이 없기 때문입니다. 실제로 새로 생성된 각 SpringBoot 프로젝트에 대해 pom.xml 파일에서 다음 플러그인을 볼 수 있습니다. 🎜<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
这个是SpringBoot官方提供的用于打包FatJar的插件,org.springframework.boot.loader
下的类其实就是通过这个插件打进去的;
下面是此插件将 loader 相关类打入 FatJar 的一个执行流程:
org.springframework.boot.maven#execute-> org.springframework.boot.maven#repackage -> org.springframework.boot.loader.tools.Repackager#repackage-> org.springframework.boot.loader.tools.Repackager#writeLoaderClasses-> org.springframework.boot.loader.tools.JarWriter#writeLoaderClasses
最终的执行方法就是下面这个方法,通过注释可以看出,该方法的作用就是将 spring-boot-loader 的classes 写入到 FatJar 中。
/** * Write the required spring-boot-loader classes to the JAR. * @throws IOException if the classes cannot be written */ @Override public void writeLoaderClasses() throws IOException { writeLoaderClasses(NESTED_LOADER_JAR); }
Spring Boot项目被编译以后,在targert
目录下存在两个jar文件:一个是xxx.jar
和xxx.jar.original
。
其中xxx.jar.original
是maven编译后的原始jar文件,即标准的java jar。该文件仅包含应用本地资源。 如果单纯使用这个jar,无法正常运行,因为缺少依赖的第三方资源。
因此spring-boot-maven-plugin
插件对这个xxx.jar.original
再做一层加工,引入第三方依赖的jar包等资源,将其 "repackage"
为xxx.jar
。可执行Jar的文件结构如下图所示:
. ├── BOOT-INF │ ├── classes │ │ ├── application.properties # 用户-配置文件 │ │ └── com │ │ └── glmapper │ │ └── bridge │ │ └── boot │ │ └── BootStrap.class # 用户-启动类 │ └── lib │ ├── jakarta.annotation-api-1.3.5.jar │ ├── jul-to-slf4j-1.7.28.jar │ ├── log4j-xxx.jar # 表示 log4j 相关的依赖简写 ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── com.glmapper.bridge.boot │ └── guides-for-jarlaunch │ ├── pom.properties │ └── pom.xml └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── MainMethodRunner.class ├── PropertiesLauncher$1.class ├── PropertiesLauncher$ArchiveEntryFilter.class ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class ├── PropertiesLauncher.class ├── WarLauncher.class ├── archive │ ├── # 省略 ├── data │ ├── # 省略 ├── jar │ ├── # 省略 └── util └── SystemPropertyUtils.class
META-INF: 存放元数据。MANIFEST.MF 是 jar 规范,Spring Boot 为了便于加载第三方 jar 对内容做了修改;
org: 存放Spring Boot 相关类,比如启动时所需的 Launcher 等;
BOOT-INF/class: 存放应用编译后的 class 文件;
BOOT-INF/lib: 存放应用依赖的 JAR 包。
Spring Boot的MANIFEST.MF
和普通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: 是java -jar
启动引导类,但这里不是项目中的类,而是Spring Boot内部的JarLauncher。
Start-Class: 这个才是正在要执行的应用内部主类
所以java -jar
启动的时候,加载运行的是JarLauncher。Spring Boot内部如何通过JarLauncher 加载Start-Class 执行呢?为了更清楚加载流程,我们先介绍下java -jar
是如何完成类加载逻辑的。
这里简单说下java -jar
启动时是如何完成记载类加载的。Java 采用了双亲委派机制,Java语言系统自带有三个类加载器:
Bootstrap CLassloder: 最顶层的加载类,主要加载核心类库
Extention ClassLoader: 扩展的类加载器,加载目录%JRE_HOME%/lib/ext
目录下的jar包和class文件。 还可以加载-D java.ext.dirs选项指定的目录。
AppClassLoader: 是应用加载器。
默认情况下通过java -classpath
,java -cp
,java -jar
使用的类加载器都是AppClassLoader。 普通可执行jar通过java -jar
启动后,使用AppClassLoader加载Main-class
类。 如果第三方jar不在AppClassLoader里,会导致启动时候会报ClassNotFoundException。
例如在Spring Boot可执行jar的解压目录下,执行应用的主函数,就直接报该错误:
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
at com.glmapper.bridge.boot.BootStrap.main(BootStrap.java:13)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
从异常堆栈来看,是因为找不到SpringApplication
这个类;这里其实还是比较好理解的,BootStrap
类中引入了SpringApplication
,但是这个类是在BOOT-INF/lib
下的,而java指令在启动时未指明classpath
,依赖的第三方jar无法被加载。
Spring Boot JarLauncher启动时,会将所有依赖的内嵌 jar (BOOT-INF/lib 目录下) 和class(BOOT-INF/classes 目录)都加入到自定义的类加载器LaunchedURLClassLoader中,并用这个ClassLoder去加载MANIFEST.MF配置Start-Class,则不会出现类找不到的错误。
LaunchedURLClassLoader是URLClassLoader的子类, URLClassLoader会通过URL[] 来搜索类所在的位置。Spring Boot 则将所需要的内嵌文档组装成URL[],最终构建LaunchedURLClassLoader类。
有了以上知识的铺垫,我们看下整个 FatJar 启动的过程会是怎样。为了以便查看源码和远程调试,可以在 pom.xml 引入下面的配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-loader</artifactId> </dependency>
简单概括起来可以分为几步:
java -jar 启动,AppClassLoader 则会加载 MANIFEST.MF 配置的Main-Class, JarLauncher。
JarLauncher启动时,注册URL关联协议。
获取所有内嵌的存档(内嵌jar和class)
根据存档的URL[]构建类加载器。
然后用这个类加载器加载Start-Class。 保证这些类都在同一个ClassLoader中。
위 내용은 Springboot의 FatJar 및 Jar란 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!