本系列基于Spring Boot 2.2.6.RELEASE编写。
为了探究Spring Boot应用的Jar包结构,我们使用Maven构建Spring Boot应用。
创建Spring Boot应用
首先创建一个标准Maven工程(Maven3.6.2):
1 | mvn archetype:generate -DinteractiveMode=false -DgroupId=“com.evan” -DartifactId=first-spring-boot-application |
Build成功后,增加Spring Boot依赖:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
将App.java的内容修改为:
1 | package com.evan; |
按照官方文档中运行spring boot工程的命令:mvn spring-boot:run
Build会失败,提示找不到spring-boot插件,因为目前还没有像官方样例那样引入父POM
1 | <parent> |
spring-boot插件就声明在这个父POM中。引入之后,通过spring-boot插件就可以运行spring boot工程。
spring-boot-starter-parent这个父POM有以下特性:
- 使用Java 1.8作为默认编译级别
- UTF-8源文件编码
- 继承了spring-boot-dependencies pom进行依赖管理,使得用户可以不指定依赖的version
- 可执行的Maven goal:repackage
- 合理的资源过滤(resource filtering)
- 合理的插件配置(exec plugin, Git commit ID, and shade)
- Sensible resource filtering for
application.propertiesandapplication.ymlincluding profile-specific files (for example,application-dev.propertiesandapplication-dev.yml)
创建Spring Boot应用可执行JAR(Executable JAR)
可执行Jar也叫“fat jar”,是包含编译类以及代码运行所有需要的jar依赖的压缩文件。
要创建一个可执行jar,需要将spring-boot-maven-plugin插件添加到pom.xml中。
1 | <build> |
运行mvn package后,会在工程的target目录下生成first-spring-boot-application-1.0-SNAPSHOT.jar(16.7MB)。在target目录中,还包含有一个first-spring-boot-application-1.0-SNAPSHOT.jar.original文件(3KB)。
执行java -jar .\target\first-spring-boot-application-1.0-SNAPSHOT.jar,工程会像使用mvn spring-boot:run一样,正常运行。
可执行JAR格式
Executable jars and Java
Java does not provide a standard way to load nested jar files (jar files that are themselves contained within a jar). This can be problematic if you are looking to distribute a self-contained application.
To solve this problem, many developers use “uber” jars. An uber jar packages all the classes from all the application’s dependencies into a single archive. The problem with this approach is that it becomes hard to see which libraries are in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars.
Spring Boot takes a different approach and lets you actually nest jars directly.
spring-boot-loader 模块让 Spring Boot 支持 jar 和 war 文件。
可执行JAR
将first-spring-boot-application-1.0-SNAPSHOT.jar.original文件解压:
1 | first-spring-boot-application-1.0-SNAPSHOT.jar.original |
该文件是Maven打包后未经Spring Boot repackage的原始jar文件,只包含了本地资源(如编译后的classes目录下的资源文件),未引入第三方依赖资源。
将first-spring-boot-application-1.0-SNAPSHOT.jar文件解压,Spring Boot Loader兼容的jar文件目录结构如下:
1 | first-spring-boot-application-1.0-SNAPSHOT.jar |
- BOOT-INF/classes 目录存放应用编译后的class文件
- BOOT-INF/lib 目录存放应用依赖的JAR包
- META-INF/ 目录存放应用相关的元信息,如MANIFEST.MF文件
- org/ 目录存放Spring Boot相关的class文件
相比较之下,可执行jar多了一个BOOT-INF目录,这个目录的结构模仿了JAVA EE Web应用的WEB-INF目录结构。
org/目录下的文件并非项目中的文件,是由spring-boot-maven-plugin插件repackage进来的。
可执行WAR
1 | example.war |
WEB-INF/lib目录存放依赖,WEB-INF/lib-provided目录存放使用内嵌容器时需要,部署到传统容器时不需要的依赖(即
运行可执行JAR
根据Java官方文档的规定,java -jar命令引导的具体启动类必须配置在MANIFEST.MF资源的Main-Class属性中。
first-spring-boot-application-1.0-SNAPSHOT.jar解压出的MANIFEST.MF内容:
1 | Manifest-Version: 1.0 |
org.springframework.boot.loader.Launcher类是一个特殊的bootstrap类,它作为可执行jar的入口(entry point),用于设置适当的URLClassLoader并最终调用应用的main方法。它有三个子类JarLauncher、WarLauncher和PropertiesLauncher,均放在org/springframework/boot/loader/目录下。它们是用来从指定目录(而不是CLASSPATH)中寻找嵌套jar文件和war文件,然后加载其中的资源(.class文件等等)。JarLauncher和WarLauncher的寻找路径是固定的,JarLauncher寻找BOOT-INF/lib/目录,WarLauncher寻找WEB-INF/lib/目录。PropertiesLauncher默认从BOOT-INF/lib/目录寻找,可以通过设置环境变量LOADER_PATH或者loader.properties文件中的loader.path属性来添加额外的寻找路径。
无需在MANIFEST.MF中指定Class-Path属性,classpath可以由嵌套jars推断出来。
应用实际的启动类应该定义在MANIFEST.MF中的Start-Class属性,具体的Launcher读取此属性,获取启动类后读取main方法,反射调用。Launcher同进程调用启动类的main方法,并在启动前准备好Class Path。