소개
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 是什么
JAR简介
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还是可执行JAR解压后都包含两部分:META-INF
目录(元数据)和package
目录(编译后的class)。这种普通的jar不包含第三方依赖包,只包含应用自身的配置文件、class 等。
. ├── META-INF │ ├── MANIFEST.MF #定义 └── org # 包路径(存放编译后的class) └── springframework
描述文件MANIFEST.MF
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包了。
FatJar有什么不同
什么是FatJar?
普通的jar只包含当前 jar的信息,不含有第三方 jar。当内部依赖第三方jar时,直接运行则会报错,这时候需要将第三方jar内嵌到可执行jar里。将一个jar及其依赖的三方jar全部打到一个包中,这个包即为 FatJar。
SpringBoot FatJar解决方案
Spring Boot
为了解决内嵌jar问题,提供了一套FatJar解决方案,分别定义了jar目录结构和 MANIFEST.MF
。在编译生成可执行 jar 的基础上,使用spring-boot-maven-plugin
按Spring Boot 的可执行包标准repackage
실행 가능한 jar을 빌드할 수 있습니다. Spring Boot에는 컨테이너가 내장되어 있으며 java -jar
명령을 통해 애플리케이션을 직접 시작할 수 있습니다.
FAT JAR
의 시작 뒤에 숨은 원칙을 알아보도록 하겠습니다. 이 기사는 주로 다음 부분을 포함하고 있습니다:- 🎜JAR이란 무엇입니까🎜. 먼저,
java -jar
가 무엇을 하는지 알기 전에 jar가 무엇인지 이해해야 합니다. 🎜 - 🎜🎜FatJar🎜의 차이점은 무엇인가요? Spring Boot에서 제공하는 실행 가능한 jar과 일반 jar의 차이점은 무엇입니까? 🎜
- 🎜🎜시작 시 클래스 로딩 원칙🎜. 시작하는 동안 클래스로더는 무엇을 합니까? Spring Boot는 사용자 정의 클래스 로더를 통해 임베디드 패키지의 로딩 문제를 어떻게 해결합니까? 🎜
- 🎜🎜입문의 전체 과정🎜. 마지막으로 앞선 세 부분의 내용을 통합하고 소스코드를 분석해 스타트업을 완성하는 방법을 살펴본다. 🎜 🎜🎜JAR이란 무엇입니까🎜
- 🎜🎜실행할 수 없는 JAR🎜. 패키징 시
main-class
를 지정할 필요가 없으며 실행할 수 없습니다. 일반 jar 패키지는 다른 프로젝트에서 의존하는 데 사용될 수 있습니다. 🎜 - 🎜🎜실행 가능한 JAR🎜. jar 패키지를 빌드할 때
main-class
클래스가 지정됩니다.java -jar xxx.jar
명령을 사용하여main-class
를 실행할 수 있습니다. code> >main 메소드는 jar 패키지를 실행합니다. 실행 가능한 jar 패키지 🎜는 다른 프로젝트에 의존할 수 없습니다. 🎜 🎜
JAR 소개
🎜JAR 파일(Java Archive, 영어: 🎜J🎜ava 🎜AR🎜chive)은 소프트웨어 패키지 파일 형식으로, 일반적으로 대용량 다수의 Java 클래스 파일, 관련 메타데이터 및 리소스(텍스트, 이미지 등) 파일을 하나의 파일로 모아 Java 플랫폼 응용 소프트웨어 또는 라이브러리를 배포합니다. 간단히 이해하면 실제로는 압축된 패키지이므로 JAR 파일의 내용을 추출하려면 표준 압축 풀기 소프트웨어를 사용하여 내용을 추출할 수 있습니다. 또는 Java 가상 머신과 함께 제공되는jar -xf foo.jar
명령을 사용하여 해당 jar 파일의 압축을 해제하세요. 🎜🎜🎜JAR은 간단히 두 가지 범주로 나눌 수 있습니다: 🎜🎜JAR 구조
🎜패키지 구조🎜🎜실행 불가능한 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
파일입니다. 🎜주요 구성 정보🎜는 다음과 같습니다. 🎜- 🎜Manifest-Version: 매니페스트 파일의 버전을 정의하는 데 사용됩니다. 예: Manifest-Version: 1.0🎜
- 🎜Created-By: 파일 생성기를 선언합니다. 일반적으로 이 속성은 jar 명령줄 도구에 의해 생성됩니다. 예: Created-By: Apache Ant 1.5.1🎜 🎜Signature-Version: jar 파일의 정의 서명된 버전🎜
- 🎜Class-Path: 애플리케이션 또는 클래스 로더는 이 값을 사용하여 내부 클래스 검색 경로를 구축합니다. 실행 가능한 jar 패키지. 🎜 🎜🎜위는 🎜일반 jar 패키지🎜의 속성입니다. 🎜runnable jar 패키지🎜의 .MF 파일에는
mian-class
또는 start도 있습니다. -class 및 기타 속성. 외부 jar 패키지를 사용하는 경우 lib 경로 및 기타 정보도 MF 파일에 구성됩니다. 🎜자세한 내용은🎜참조: maven이 MANIFEST.MF 파일에 콘텐츠를 추가하는 방법🎜🎜 🎜runnable jar 패키지🎜 및 🎜일반 jar 패키지🎜의 디렉터리 구조에는 특별히 고정된 패턴이 없습니다. 구조에 관계없이 .MF 파일에 jar 패키지 정보를 설정하면 jar 패키지를 정상적으로 사용할 수 있습니다. 🎜🎜FatJar의 차이점은 무엇인가요?🎜<h4 id="FatJar가-무엇인가요">FatJar가 무엇인가요? </h4>🎜일반 jar에는 현재 jar에 대한 정보만 포함되며 타사 jar는 포함되지 않습니다. 내부적으로 타사 jar를 사용하는 경우 직접 실행하면 오류가 보고됩니다. 이 경우 타사 jar를 실행 가능한 jar에 포함해야 합니다. 🎜병과 이에 종속된 타사 항아리를 하나의 패키지에 담는 것이 FatJar입니다. 🎜🎜<h4 id="SpringBoot-FatJar-솔루션">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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

javaispopularforcross-platformdesktopapplicationsduetoits "writeonce, runanywhere"철학

Java에서 플랫폼 별 코드를 작성하는 이유에는 특정 운영 체제 기능에 대한 액세스, 특정 하드웨어와 상호 작용하고 성능 최적화가 포함됩니다. 1) JNA 또는 JNI를 사용하여 Windows 레지스트리에 액세스하십시오. 2) JNI를 통한 Linux 특이 적 하드웨어 드라이버와 상호 작용; 3) 금속을 사용하여 JNI를 통해 MacOS의 게임 성능을 최적화하십시오. 그럼에도 불구하고 플랫폼 별 코드를 작성하면 코드의 이식성에 영향을 미치고 복잡성을 높이며 잠재적으로 성능 오버 헤드 및 보안 위험을 초래할 수 있습니다.

Java는 Cloud-Native Applications, Multi-Platform 배포 및 교차 운용성을 통해 플랫폼 독립성을 더욱 향상시킬 것입니다. 1) Cloud Native Applications는 Graalvm 및 Quarkus를 사용하여 시작 속도를 높입니다. 2) Java는 임베디드 장치, 모바일 장치 및 양자 컴퓨터로 확장됩니다. 3) Graalvm을 통해 Java는 Python 및 JavaScript와 같은 언어와 완벽하게 통합되어 언어 교차 수용 가능성을 향상시킵니다.

Java의 강력한 유형 시스템은 유형 안전, 통합 유형 변환 및 다형성을 통해 플랫폼 독립성을 보장합니다. 1) 유형 안전성 런타임 오류를 피하기 위해 컴파일 시간에 유형 검사를 수행합니다. 2) 통합 유형 변환 규칙은 모든 플랫폼에서 일관성이 있습니다. 3) 다형성 및 인터페이스 메커니즘은 코드가 다른 플랫폼에서 일관되게 행동하게 만듭니다.

JNI는 Java의 플랫폼 독립성을 파괴 할 것입니다. 1) JNI는 특정 플랫폼에 대한 로컬 라이브러리를 요구합니다. 2) 대상 플랫폼에서 로컬 코드를 컴파일하고 연결해야합니다. 3) 운영 체제 또는 JVM의 다른 버전은 다른 로컬 라이브러리 버전을 필요로 할 수 있습니다.

신흥 기술은 위협을 일으키고 Java의 플랫폼 독립성을 향상시킵니다. 1) Docker와 같은 클라우드 컴퓨팅 및 컨테이너화 기술은 Java의 플랫폼 독립성을 향상 시키지만 다양한 클라우드 환경에 적응하도록 최적화되어야합니다. 2) WebAssembly는 Graalvm을 통해 Java 코드를 컴파일하여 플랫폼 독립성을 확장하지만 성능을 위해 다른 언어와 경쟁해야합니다.

다른 JVM 구현은 플랫폼 독립성을 제공 할 수 있지만 성능은 약간 다릅니다. 1. OracleHotspot 및 OpenJDKJVM 플랫폼 독립성에서 유사하게 수행되지만 OpenJDK에는 추가 구성이 필요할 수 있습니다. 2. IBMJ9JVM은 특정 운영 체제에서 최적화를 수행합니다. 3. Graalvm은 여러 언어를 지원하며 추가 구성이 필요합니다. 4. AzulzingJVM에는 특정 플랫폼 조정이 필요합니다.

플랫폼 독립성은 여러 운영 체제에서 동일한 코드 세트를 실행하여 개발 비용을 줄이고 개발 시간을 단축시킵니다. 구체적으로, 그것은 다음과 같이 나타납니다. 1. 개발 시간을 줄이면 하나의 코드 세트 만 필요합니다. 2. 유지 보수 비용을 줄이고 테스트 프로세스를 통합합니다. 3. 배포 프로세스를 단순화하기위한 빠른 반복 및 팀 협업.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SecList
SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

드림위버 CS6
시각적 웹 개발 도구

Eclipse용 SAP NetWeaver 서버 어댑터
Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)
