一、场景介绍
-
众所周知 Spring Boot 应用是目前企业最主流的一套快速搭建项目的脚手架 -
在企业应用搭建过程中,所有繁琐的 XMl 配置,都被注解取代,约定大于配置、自动装配等功能大大提高了项目框架搭建的效率 -
Spring Boot 项目部署采用 spring-boot-maven-plugin 插件打出来的 JAR 包,是可独立运行的(依赖包、业务包、服务器容器一并打成 JAR 包),这一点方便了部署和发布 -
但是也正因为 Fat Jar 这一特征,针对不同的业务部署场景,也需要不同的部署方案:
-
在实际的开发过程中,一般项目框架搭建好以后,基础依赖发生变化的可能性不是很大,所以我们在进行外网快速部署的时候,可以通过这个特征来综合考虑打包方式 -
为适应以上场景的描述,我们将介绍两种打包方式,以备大家酌情选择
二、项目搭建
-
新建 Maven 父子工程 example
- example-common
- CommonUtil.java
- pom.xml
- example-service
- ServiceApplication.java
- pom.xml
- pom.xml
P.S
-
example 为 example-common 和 example-service 的父工程 -
example-service 依赖 example-common 模块 -
example-service 模块包含主启动类,为标准都 spring boot 工程,example-common 模块无启动类 -
父工程 pom <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 模块坐标信息 GAV -->
<groupId>com.rambo</groupId>
<artifactId>example</artifactId>
<packaging>pom</packaging>
<version>V1.0.0.1</version>
<name>${project.artifactId}</name>
<description>父子模块示例工程 —— 基础父工程</description>
<!-- 子模块列表 -->
<modules>
<module>example-common</module>
<module>example-service</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.5.2</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
</build>
</project>
-
example-common 模块 pom <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父工程 GAV -->
<parent>
<artifactId>example</artifactId>
<groupId>com.rambo</groupId>
<version>V1.0.0.1</version>
</parent>
<!-- 本工程模块 AV -->
<artifactId>example-common</artifactId>
<version>V1.0.0.1</version>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>无启动类的示例通用模块 —— 通用工具</description>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
-
example-service 模块 pom <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父工程 GAV -->
<parent>
<artifactId>example</artifactId>
<groupId>com.rambo</groupId>
<version>V1.0.0.1</version>
</parent>
<!-- 本工程模块 AV -->
<artifactId>example-service</artifactId>
<version>V1.0.0.1</version>
<packaging>jar</packaging>
<name>example-service</name>
<description>有启动类的示例服务模块 —— 示例服务</description>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 示例通用工具模块 -->
<dependency>
<groupId>com.rambo</groupId>
<artifactId>example-common</artifactId>
<version>V1.0.0.1</version>
</dependency>
<!-- 方便解读编译后的字节码 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
-
example-common 和 example-service 的示例代码如下
-
example-common 模块 public class CommonUtil {
public static void info() {
System.out.println("This is info from CommonUtil.class");
}
}
-
example-service 模块 @SpringBootApplication
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
CommonUtil.info();
}
}
二、打包方案一(Fat Jar)
-
在 example-service 模块的 pom 文件中添加 spring-boot-maven-plugin 的打包插件 <build>
<plugins>
<!-- Fat Jar 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
-
打包完成以后,解压 Fat Jar 进行解析
-
example-service 模块的 MANIFEST.MF 文件内容如下 Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: rambo
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.rambo.service.ServiceApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.5.2
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Main-Class: org.springframework.boot.loader.JarLauncher
-
BOOT-INFO/class 目录存放应用编译后的 class 文件 -
BOOT-INFO/lib 目录存放应用依赖的 JAR 包,包括 Tomcat 容器 -
META-INF/ 目录存放应用相关的元信息,如:MANIFEST.MF -
org/ 目录存放 Spring Boot 相关的 class 文件 P.S 由于 Fat Jar 是采用 org.springframework.boot.loader.JarLauncher 代理启动我们的 Start-Class 应用的,我们将 Fat Jar 解压后进入根目录采用如下命令启动也是可以的 java org.springframework.boot.loader.JarLauncher
关于 JarLauncher 和 WarLauncher 代理启动的原理,大家可以参考 《Spring Boot 代理启动 —— JarLauncher & WarLauncher》 一文 -
启动测试 ? ~/WorkSpace/example/example-service/target/ java -jar example-service.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-07-14 16:16:20.605 INFO 44058 --- [ main] com.rambo.service.ServiceApplication : Starting ServiceApplication using Java 1.8.0_271 on Rambos-MacBook-Pro.local with PID 44058 (/Users/rambo/WorkSpace/example/example-service/target/example-service.jar started by rambo in /Users/rambo/WorkSpace/example/example-service/target)
2021-07-14 16:16:20.607 INFO 44058 --- [ main] com.rambo.service.ServiceApplication : No active profile set, falling back to default profiles: default
2021-07-14 16:16:21.584 INFO 44058 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9999 (http)
2021-07-14 16:16:21.596 INFO 44058 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-07-14 16:16:21.596 INFO 44058 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.48]
2021-07-14 16:16:21.665 INFO 44058 --- [ main] o.a.c.c.C.[.[localhost].[/example] : Initializing Spring embedded WebApplicationContext
2021-07-14 16:16:21.665 INFO 44058 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1003 ms
2021-07-14 16:16:22.012 INFO 44058 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9999 (http) with context path '/example'
2021-07-14 16:16:22.022 INFO 44058 --- [ main] com.rambo.service.ServiceApplication : Started ServiceApplication in 2.238 seconds (JVM running for 2.667)
This is info from CommonUtil.class
-
总结
三、打包方案二(Thin Jar)
-
在 example-service 模块的 pom 文件中替换 spring-boot-maven-plugin 和 maven-dependency-plugin 插件,实现业务 JAR 和依赖 JAR 分开打包 <build>
<plugins>
<!-- Thin Jar 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<!-- 指定主启动类
<mainClass>com.rambo.service.ServiceApplication</mainClass>
-->
<!-- None in one jar 必须是 ZIP, All in one jar 可以是 JAR -->
<layout>ZIP</layout>
<!-- 构建完整可直接运行的执行程序 -->
<executable>true</executable>
<!-- 使用外部配置文件,jar包里没有资源文件 -->
<addResources>true</addResources>
<outputDirectory>${project.build.directory}</outputDirectory>
<includes>
<!-- 根据 POM 文件获取依赖的 JAR 包 -->
<!-- mvn dependency:copy-dependencies -DoutputDirectory=/path/to/lib -DincludeScope=runtime -->
<!-- 根据外部 lib 的依赖启动 JAR 包 -->
<!-- ava -jar -Dloader.path=/path/to/lib blog-web.jar >> web.log -->
<!-- 编译出不带 lib 文件夹的 JAR 包 -->
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 拷贝依赖插件,也可以通过 mvn dependency:copy-dependencies -DoutputDirectory=/path/to/lib -DincludeScope=runtime 命令来收集依赖包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 将公共依赖导出至 lib 文件夹 -->
<outputDirectory>${project.build.directory}/lib/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
-
解压 Thin Jar 进行解析
-
example-service 模块的 MANIFEST.MF 文件内容如下 Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: rambo
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.rambo.service.ServiceApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.5.2
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Main-Class: org.springframework.boot.loader.PropertiesLauncher
-
BOOT-INFO/class 目录存放应用编译后的 class 文件 -
BOOT-INFO/lib 该目录下的公共依赖都没有被打进来 -
META-INF/ 目录存放应用相关的元信息,如:MANIFEST.MF -
org/ 目录存放 Spring Boot 相关的 class 文件 P.S 由于 Thin Jar 是采用 org.springframework.boot.loader.PropertiesLauncher 代理启动我们的 Start-Class 应用的,我们将 Thin Jar 解压后进入根目录采用如下命令启动也是可以的
java -jar -Dloader.path=./lib example-service.jar
PropertiesLauncher 可以通过外部属性来启动 -
打包后的结构,通过下图可以看出,公共的以来都保存在 lib 文件夹中,和 Thin Jar 同级 -
启动测试 ? ~/WorkSpace/example/example-service/target/ java -jar -Dloader.path=./lib example-service.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-07-14 16:50:49.247 INFO 44580 --- [ main] com.rambo.service.ServiceApplication : Starting ServiceApplication using Java 1.8.0_271 on Rambos-MacBook-Pro.local with PID 44580 (/Users/rambo/WorkSpace/example/example-service/target/example-service.jar started by rambo in /Users/rambo/WorkSpace/example/example-service/target)
2021-07-14 16:50:49.249 INFO 44580 --- [ main] com.rambo.service.ServiceApplication : No active profile set, falling back to default profiles: default
2021-07-14 16:50:50.253 INFO 44580 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9999 (http)
2021-07-14 16:50:50.264 INFO 44580 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-07-14 16:50:50.265 INFO 44580 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.48]
2021-07-14 16:50:50.329 INFO 44580 --- [ main] o.a.c.c.C.[.[localhost].[/example] : Initializing Spring embedded WebApplicationContext
2021-07-14 16:50:50.329 INFO 44580 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1031 ms
2021-07-14 16:50:50.719 INFO 44580 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9999 (http) with context path '/example'
2021-07-14 16:50:50.729 INFO 44580 --- [ main] com.rambo.service.ServiceApplication : Started ServiceApplication in 3.008 seconds (JVM running for 3.427)
This is info from CommonUtil.class
-
总结
|