您好,登录后才能下订单哦!
# Spring Boot中的JAR是怎样的
## 引言
在现代Java开发中,Spring Boot已经成为构建企业级应用的首选框架。其核心特性之一就是能够将应用程序打包成一个独立的、可执行的JAR文件(通常称为"fat JAR"或"uber JAR")。这种打包方式彻底改变了传统Java应用的部署模式,使开发者能够轻松地构建、发布和运行Spring Boot应用。本文将深入探讨Spring Boot JAR的结构、工作原理、创建过程以及相关的高级主题,帮助开发者全面理解这一关键技术。
## 一、传统JAR与Spring Boot可执行JAR的区别
### 1.1 标准JAR文件结构
传统的Java JAR(Java Archive)文件遵循Zip格式,包含编译后的.class文件、资源文件和可选的META-INF/MANIFEST.MF清单文件。典型结构如下:
myapp.jar ├── META-INF │ └── MANIFEST.MF ├── com │ └── example │ └── MyClass.class └── application.properties
清单文件中通常指定Main-Class属性,指示Java虚拟机从哪个类开始执行。
### 1.2 Spring Boot可执行JAR的独特之处
Spring Boot的可执行JAR在标准JAR基础上进行了扩展和创新:
1. **嵌套JAR支持**:采用特殊布局允许JAR中包含其他JAR(依赖库)
2. **自定义类加载器**:使用Launcher类实现独特的嵌套JAR加载机制
3. **分层优化**:支持分层打包以加速容器环境中的启动速度
4. **嵌入式服务器**:默认包含Tomcat/Jetty等Web服务器
这种设计使应用能够真正做到"一次构建,到处运行",无需预先安装服务器或管理复杂的类路径。
## 二、Spring Boot可执行JAR的详细结构
解压一个典型的Spring Boot JAR,我们可以看到以下结构:
springboot-app.jar ├── BOOT-INF │ ├── classes │ │ └── com/example/MyApplication.class │ └── lib │ ├── spring-boot-2.7.3.jar │ ├── spring-core-5.3.22.jar │ └── … ├── META-INF │ ├── MANIFEST.MF │ └── maven/com.example/springboot-app/pom.properties ├── org │ └── springframework │ └── boot │ └── loader │ ├── JarLauncher.class │ ├── LaunchedURLClassLoader.class │ └── … └── application.properties
### 2.1 关键目录解析
1. **BOOT-INF目录**:
- classes:包含应用程序自身的编译类文件
- lib:存放所有依赖的第三方库JAR文件
2. **META-INF目录**:
- MANIFEST.MF:包含特殊的启动配置信息
- maven:包含从构建系统继承的元数据
3. **org/springframework/boot/loader**:
- 包含Spring Boot的自定义类加载器实现
- 负责处理嵌套JAR的加载逻辑
### 2.2 清单文件(MANIFEST.MF)分析
Spring Boot生成的MANIFEST.MF包含以下关键属性:
```manifest
Manifest-Version: 1.0
Spring-Boot-Version: 2.7.3
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.MyApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Main-Class
指向Spring Boot的JarLauncher而非应用主类Start-Class
才是实际的应用程序入口当执行java -jar springboot-app.jar
时,JVM会按照以下顺序工作:
Spring Boot的LaunchedURLClassLoader扩展了URLClassLoader,重写了以下关键行为:
这种设计解决了标准URLClassLoader无法直接加载嵌套JAR中类的问题。
在pom.xml中配置spring-boot-maven-plugin:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.3</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行打包命令:
mvn clean package
在build.gradle中应用插件并配置:
plugins {
id 'org.springframework.boot' version '2.7.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
bootJar {
archiveFileName = 'myapp.jar'
layered {
enabled = true
}
}
执行打包:
gradle bootJar
Spring Boot 2.3+引入了分层JAR概念,优化Docker镜像构建:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
生成的JAR包含layers.idx文件定义分层:
- "dependencies":
- "BOOT-INF/lib/dependency1.jar"
- "BOOT-INF/lib/dependency2.jar"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "BOOT-INF/lib/snapshot1.jar"
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/libs/"
通过插件配置添加自定义属性:
<plugin>
<configuration>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addBuildEnvironmentEntries>true</addBuildEnvironmentEntries>
</manifest>
</configuration>
</plugin>
使用exclude元素移除不需要的依赖:
<plugin>
<configuration>
<excludes>
<exclude>
<groupId>org.unwanted</groupId>
<artifactId>dependency</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
jlink --module-path $JAVA_HOME/jmods:mods \
--add-modules com.example.app \
--output customjre
<plugin>
<configuration>
<compress>true</compress>
</configuration>
</plugin>
@ComponentScan(basePackages = "com.myapp")
spring.main.lazy-initialization=true
mvn spring-boot:build-image
症状:NoClassDefFoundError或ClassNotFoundException
解决方案:
1. 检查依赖是否真正打包进BOOT-INF/lib
2. 确认没有不兼容的依赖版本
3. 使用mvn dependency:tree
分析依赖冲突
症状:getResourceAsStream返回null
原因:资源路径需要相对于BOOT-INF/classes
修复:
// 使用ClassLoader加载资源
InputStream is = getClass().getClassLoader()
.getResourceAsStream("templates/index.html");
优化方案: 1. 使用分层JAR 2. 排除不必要的依赖 3. 拆分微服务 4. 考虑War部署
native-image -jar springboot-app.jar
更高效的分层机制
模块化支持改进
FROM eclipse-temurin:17-jdk as builder
WORKDIR application
COPY . .
RUN ./gradlew bootJar
FROM eclipse-temurin:17-jre
COPY --from=builder application/build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
pack build myapp --builder paketobuildpacks/builder:base
Spring Boot的可执行JAR设计体现了”约定优于配置”的核心理念,通过创新的打包方式和启动机制,极大地简化了Java应用的部署和分发流程。理解其内部工作原理不仅有助于解决实际问题,还能启发我们设计更加优雅的架构。随着云原生技术的发展,Spring Boot的打包策略也在不断进化,但核心思想始终不变:让开发者专注于业务逻辑,而非基础设施的搭建。
jar tf springboot-app.jar
jar xf springboot-app.jar BOOT-INF/lib/spring-core-5.3.22.jar
mvn dependency:tree -Dincludes=org.springframework
”`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。