>  기사  >  Java  >  Springboot의 FatJar 및 Jar란 무엇입니까?

Springboot의 FatJar 및 Jar란 무엇입니까?

WBOY
WBOY앞으로
2023-05-11 09:58:131832검색

    소개

    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-classmain方法,运行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-classstart-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 소개

      🎜JAR 파일(Java Archive, 영어: 🎜J🎜ava 🎜AR🎜chive)은 소프트웨어 패키지 파일 형식으로, 일반적으로 대용량 다수의 Java 클래스 파일, 관련 메타데이터 및 리소스(텍스트, 이미지 등) 파일을 하나의 파일로 모아 Java 플랫폼 응용 소프트웨어 또는 라이브러리를 배포합니다. 간단히 이해하면 실제로는 압축된 패키지이므로 JAR 파일의 내용을 추출하려면 표준 압축 풀기 소프트웨어를 사용하여 내용을 추출할 수 있습니다. 또는 Java 가상 머신과 함께 제공되는 jar -xf foo.jar 명령을 사용하여 해당 jar 파일의 압축을 해제하세요. 🎜🎜🎜JAR은 간단히 두 가지 범주로 나눌 수 있습니다: 🎜🎜
      • 🎜🎜실행할 수 없는 JAR🎜. 패키징 시 main-class를 지정할 필요가 없으며 실행할 수 없습니다. 일반 jar 패키지는 다른 프로젝트에서 의존하는 데 사용될 수 있습니다. 🎜
      • 🎜🎜실행 가능한 JAR🎜. jar 패키지를 빌드할 때 main-class 클래스가 지정됩니다. java -jar xxx.jar 명령을 사용하여 main-class를 실행할 수 있습니다. code> >main 메소드는 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>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.jarxxx.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 -classpathjava -cpjava -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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제