<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<!-- 可选配置项,如mainClass属性指定入口类 -->
<mainClass>${start-class}</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
通过mvn package
命令,Maven首先会按照标准流程构建项目,随后spring-boot-maven-plugin
会执行repackage
目标,该目标会重新包装已生成的标准JAR文件,将其转换为包含所有依赖项和适当的启动器信息的Fat JAR。这样生成的JAR可以直接通过java -jar
命令启动。
Spring Boot应用打包机制均确保了生成的包不仅包含了项目本身的类,还包含了运行时所必需的所有依赖库,以及一些特定的元数据(如MANIFEST.MF中的启动类信息)。这一特性大大简化了部署过程,并有助于提升应用的可移植性和维护性。Fat jar中的内容:
META-INF/
: 包含MANIFEST.MF文件和其他元数据信息,其中Main-Class属性指向Spring Boot的启动类加载器。
BOOT-INF/classes/
: 存放项目自身的类文件和资源文件。
BOOT-INF/lib/
: 放置所有依赖的jar包,包括Spring Boot starter依赖以及其他第三方库。(如果项目中有静态资源文件,也会在BOOT-INF下有对应的static、templates等目录)
Spring Boot启动器与Loader机制
Spring Boot应用的jar包可以直接运行主要依赖于它的启动器以及Loader机制,而对于Loader机制主要利用MANIFEST.MF文件以及其内部类加载逻辑。
MANIFEST.MF文件是什么?
MANIFEST.MF
是JAR
文件内的一个标准元数据文件,它包含了关于JAR包的基本信息和运行指令。在Spring Boot应用的jar包中,MANIFEST.MF
尤为重要,因为它设置了Main-Class
属性,指示了用于启动整个应用程序的类,这个类通常是org.springframework.boot.loader.JarLauncher
或其他由Spring Boot提供的启动器类。
Main-Class
属性指向的JarLauncher
类是Spring Boot自定义的类加载器体系的一部分。JarLauncher
继承自org.springframework.boot.loader.Launcher
,专门用于启动以Fat JAR
形式发布的Spring Boot应用。JarLauncher
负责创建一个类加载器LaunchedURLClassLoader
。
当通过java -jar
命令执行Spring Boot jar包时,JVM会依据MANIFEST.MF
中的Main-Class
启动指定的启动器。
Spring Boot的启动器类加载器LaunchedURLClassLoader
首先会读取MANIFEST.MF中的附加属性,如Start-Class
(标识应用的实际主类)和Spring-Boot-Lib
(指向内部依赖库的位置)。
启动类加载器工作流程如下:
当启动器类加载器启动时,它会根据MANIFEST.MF中的信息来组织类路径,保证所有内部的依赖库都能正确地被加载。
加载器会区分出 BOOT-INF/classes
中的应用程序类和 BOOT-INF/lib
下的依赖库,分别处理并加入到类加载器的搜索路径中。
加载器加载并执行实际的Start-Class
,即应用的主类,触发Spring Boot框架的初始化和应用的启动流程。比如示例中的应用主类:com.springboot.base.SpringBootBaseApplication
Spring Boot的启动器和加载器机制有效地实现了对自包含jar包的管理和执行,我们无需关心复杂的类路径配置和依赖加载,只需通过一个简单的命令即可启动一个完整、独立运行的应用程序。
内嵌Web容器
Spring Boot的一大特色就是能够无缝整合并内嵌多种轻量级Web容器,比如:Apache Tomcat
、Jetty
、Undertow
以及Reactor Netty
(对于响应式编程模型)。内嵌Web容器的引入极大地简化了Web应用的部署流程,我们不再需要在本地或服务器上独立安装和配置Web服务器(比如以前还要在本地安装tomcat)。
当Spring Boot应用引入了spring-boot-starter-web
依赖时,默认情况下会自动配置并启动一个内嵌的Web容器。在Spring Boot启动的过程中,内嵌容器作为应用的一部分被初始化并绑定到特定端口上,以便对外提供HTTP服务。
Spring Boot内嵌web容器的优点在于简化部署,通过将Web容器内置于应用中,只需分发单一的JAR文件,就能在干净的环境中运行应用,避免了与现有Web服务器版本冲突或配置不当等问题;同时加快了启动速度,尤其在开发和测试阶段,实现近乎即时的热重启;提高了应用的稳定性,因为开发环境和生产环境使用相同的Web容器,降低了因环境差异导致的问题;此外,虽然容器是内嵌的,但仍然可以进行全面的配置调整,如端口、连接数、SSL设置等,以满足不同场景的需求。通过内嵌Web容器,Spring Boot真正实现了“开箱即用”的理念。
自动配置与类路径扫描
Spring Boot的核心特性之一就是其强大的自动配置能力,它允许应用在几乎零配置的情况下快速启动并运行。
当应用启动时,Spring Boot会读取resource/META-INF/spring.factories
文件,该文件列出了所有可用的自动配置类。当它检测到应用环境中对应的自动配置类就会生效,通过@Configuration
注解的类创建并注册Bean到Spring容器中,从而实现Bean的自动装配。
这里说明下,在springboot3.x以后,就不在从resource/META-INF/spring.factories读取自动配置类了,而是从org.springframework.boot.autoconfigure.AutoConfiguration.imports中读取,这一点请参考文章:华为二面:SpringBoot如何自定义_Starter_?
并且Spring Boot还采用条件注解(如@ConditionalOnClass
、@ConditionalOnMissingBean
等)来智能判断何时应用特定的配置。这些注解可以根据类路径中是否存在特定类、系统属性或环境变量的值等因素,决定是否应该激活某个自动配置类。这意味着只有当满足特定条件时,相应的Bean才会被创建和注入。
而对于应用主类则是用@SpringBootApplication
注解标识。@SpringBootApplication
是一个复合注解,包含了@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
三个注解的功能。其中
@SpringBootConfiguration
是一个Spring配置类,可以替代@Configuration
注解,声明当前类是Spring配置类,里面包含了一系列@Bean
方法或@ConfigurationProperties
等配置。
@EnableAutoConfiguration
启用自动配置特性,告诉Spring Boot根据应用类路径中的依赖来自动配置Bean。Spring Boot会根据类路径扫描的结果,智能地决定哪些自动配置类应当生效。
@ComponentScan
会自动扫描和管理Spring组件,包括@Service、@Repository、@Controller和@Component等注解标注的类。通过该注解,Spring Boot能自动发现和管理应用中的各个组件,并将其注册为Spring容器中的Bean。
通过上述机制,Spring Boot能够智能识别项目依赖、自动配置Bean,并结合类路径扫描确保所有相关的组件和服务都被正确地初始化和管理,我们就可以专注于业务逻辑的开发,而不必过多考虑基础设施层面的配置问题。
Spring Boot 应用程序被打包成的jar包之所以可以直接通过 java -jar
命令运行,是因为Spring Boot在构建过程中做了一些特殊的设计和配置。具体原因:
Fat/Uber JAR: Spring Boot使用maven插件spring-boot-maven-plugin
(或Gradle对应的插件)将项目及其所有依赖项打包成一个单一的、自包含的jar文件,通常称为“Fat JAR”或“Uber JAR”。这意味着不仅包含了自己的类文件,还包含了运行应用所需的所有第三方库。
Manifest.MF: 在打包过程中,此插件会修改MANIFEST.MF文件,这是jar包中的一个元数据文件。在MANIFEST.MF中,特别指定了Main-Class
属性,该属性指向Spring Boot的一个内置的启动类(如org.springframework.boot.loader.JarLauncher
),这个启动器类知道如何正确启动Spring Boot应用程序。
嵌入式Servlet容器:Spring Boot默认集成了诸如Tomcat、Jetty或Undertow等嵌入式Web容器,使得无需外部服务器环境也能运行Web应用。
启动器类加载器:当通过java -jar
运行Spring Boot应用时,JVM会根据MANIFEST.MF中的Main-Class
找到并运行指定的启动器类。这个启动器类加载器能够解压并加载内部的依赖库,并定位到实际的应用主类(在spring-boot-starter-parent
或@SpringBootApplication
注解标记的类),进而执行其main
方法。
类路径扫描和自动配置:Spring Boot应用通过特定的类路径扫描机制和自动配置功能,能够在启动时识别出应用所依赖的服务和组件,并自动配置它们,大大简化了传统Java应用的配置和部署过程。
Spring Boot通过精心设计的打包流程和启动器类,使得生成的jar包可以直接作为一个独立的应用程序运行,极大地简化了部署和运维复杂度。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等