文章目录
- 一、场景描述
- 二、打包方式
- 场景方式一:常规 MAVEN 零散项目打包部署方式
- 场景方式二:常规 MAVEN 集中项目打包部署方式
- 场景方式三:借助 spring-boot 插件进行打包部署
一、场景描述
- 采用父子工程的模块化管理,可以解决模块版本和模块间依赖的大多数场景和问题
- 采用
spring-boot
插件进行项目的打包和部署,可以解决九成的项目持续集成和部署问题 - 在项目开发过程中,随着业务的不断复杂和模块的不断增加,一种打包和部署方式可能难以满足,所以我们需要掌握不同项目的打包和部署方式,来应对变化多端的场景和需求
二、打包方式
场景方式一:常规 MAVEN 零散项目打包部署方式
- 新建
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
模块- 所有模块的
pom
文件中不加入任何的Maven
打包插件 - 父工程
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>
</properties>
<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>
</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>
<!-- 示例通用工具模块 -->
<dependency>
<groupId>com.rambo</groupId>
<artifactId>example-common</artifactId>
<version>V1.0.0.1</version>
</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
模块
public class ServiceApplication {
public static void main(String[] args) {
CommonUtil.info();
}
}
- 在父工程的
pom
文件同级执行Maven
打包命令
- 打包命令
mvn package
- 两个模块的
MANIFEST.MF
文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
- 将上一步打出来的
jar
包放在同一个目录中,执行以下命令启动程序
- 执行命令
java -classpath example-common.jar:example-service.jar com.rambo.service.ServiceApplication
- 运行结果
~/WorkSpace/example/example-service/target/ java -classpath example-common.jar:example-service.jar com.rambo.service.ServiceApplication
This is info from CommonUtil.class
~/WorkSpace/example/example-service/target/
- 命令解释
- 语法:
java -classpath xxx.jar:yyy.jar:zzz.jar mainClassName
java -classpath
:为固定部分xxx.jar:yyy.jar:zzz.jar
: 多个依赖jar
用:
隔开mainClassName
: 为main
方法所在类的类名
场景方式二:常规 MAVEN 集中项目打包部署方式
- 示例工程还是采用第一步创建的父子工程
- 之所以说是集中项目打包的使用场景:项目创建好以后,不会随意的新增或者删减模块(言外之意就是,
A
和B
模块的创建和依赖关系固定) - 在
example-service
模块pom
文件中添加如下插件
<build>
<plugins>
<!-- 采用 maven-jar-plugin 插件来自定义 MANIFEST.MF 文件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}</finalName>
<archive>
<manifest>
<!-- 设置 MANIFEST.MF 文件中的主启动类 -->
<mainClass>com.rambo.service.ServiceApplication</mainClass>
<!-- 将需要依赖的 JAR 包,添加到 MANIFEST.MF 文件中 -->
<addClasspath>true</addClasspath>
<!-- 依赖 JAR 包读取的文件夹前缀 -->
<classpathPrefix>lib</classpathPrefix>
</manifest>
</archive>
</configuration>
</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>
<outputDirectory>${project.build.directory}/lib/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 在父工程的
pom
文件同级执行Maven
打包命令
- 打包命令
mvn package
example-common
模块的MANIFEST.MF
文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
example-service
模块的MANIFEST.MF
文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Class-Path: lib/example-common-V1.0.0.1.jar
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
Main-Class: com.rambo.service.ServiceApplication
Class-Path
:lib/example-common-V1.0.0.1.jar
应用程序所需依赖所在位置Main-Class
:com.rambo.service.ServiceApplication
应用程序主启动类所在位置
maven-dependency-plugin
插件将所有依赖包都已经自动归档在example-service
同一个目录的lib
文件夹中,执行以下命令启动程序
~/WorkSpace/example/example-service/target/ java -jar example-service.jar
This is info from CommonUtil.class
~/WorkSpace/example/example-service/target/
场景方式三:借助 spring-boot 插件进行打包部署
- 前面两种打包部署的方式中,主程序和依赖的
JAR
一般都是零散、分离的,这在云环境部署会比较麻烦,需要上传许多的依赖包(或者统一压缩并在云服务器解压),单机部署还好,如果分布式部署就会很累 - 为解决上述问题,我们可以打
fat jar
来解决 - 在有启动类的
example-service
模块将maven-jar-plugin
插件替换为spring-boot-maven
打包插件
<build>
<plugins>
<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>
- 在父工程的
pom
文件同级执行Maven
打包命令
- 打包命令
mvn package
example-common
模块的MANIFEST.MF
文件内容如下
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: rambo
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_271
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
- 通过此打包方式,
example-service
的JAR
可以直接运行,在其同级目录下可以看到一个example-service.jar.original
文件
~/WorkSpace/example/example-service/target/ pwd
/Users/rambo/WorkSpace/example/example-service/target
~/WorkSpace/example/example-service/target/ java -jar example-service.jar
This is info from CommonUtil.class
~/WorkSpace/example/example-service/target/
P.S
为什么可以直接运行?因为采用 spring-boot-maven
插件打出来的 JAR
包是一个 fat jar
,里面包含了被依赖的所有的 JAR
包程序,再通过 MANIFEST.MF
的 JarLauncher
即可启动应用程序
- 关于
example-service.jar.original
和example-service.jar
的理解
- 查看
.original
文件的依赖
~/WorkSpace/example/example-service/target/ jar tf example-service.jar.original
META-INF/
META-INF/MANIFEST.MF
com/
com/rambo/
com/rambo/service/
application.yml
com/rambo/service/ServiceApplication.class
META-INF/maven/
META-INF/maven/com.rambo/
META-INF/maven/com.rambo/example-service/
META-INF/maven/com.rambo/example-service/pom.xml
META-INF/maven/com.rambo/example-service/pom.properties
~/WorkSpace/example/example-service/target/
- 查看
jar
的依赖
~/WorkSpace/example/example-service/target/ jar tf example-service.jar
META-INF/
META-INF/MANIFEST.MF
org/
org/springframework/
org/springframework/boot/
org/springframework/boot/loader/
org/springframework/boot/loader/ClassPathIndexFile.class
org/springframework/boot/loader/ExecutableArchiveLauncher.class
org/springframework/boot/loader/JarLauncher.class
org/springframework/boot/loader/LaunchedURLClassLoader$DefinePackageCallType.class
org/springframework/boot/loader/LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
org/springframework/boot/loader/LaunchedURLClassLoader.class
org/springframework/boot/loader/Launcher.class
org/springframework/boot/loader/MainMethodRunner.class
org/springframework/boot/loader/PropertiesLauncher$1.class
org/springframework/boot/loader/PropertiesLauncher$ArchiveEntryFilter.class
org/springframework/boot/loader/PropertiesLauncher$ClassPathArchives.class
org/springframework/boot/loader/PropertiesLauncher$PrefixMatchingArchiveFilter.class
org/springframework/boot/loader/PropertiesLauncher.class
org/springframework/boot/loader/WarLauncher.class
org/springframework/boot/loader/archive/
org/springframework/boot/loader/archive/Archive$Entry.class
org/springframework/boot/loader/archive/Archive$EntryFilter.class
org/springframework/boot/loader/archive/Archive.class
org/springframework/boot/loader/archive/ExplodedArchive$AbstractIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$ArchiveIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$EntryIterator.class
org/springframework/boot/loader/archive/ExplodedArchive$FileEntry.class
org/springframework/boot/loader/archive/ExplodedArchive$SimpleJarFileArchive.class
org/springframework/boot/loader/archive/ExplodedArchive.class
org/springframework/boot/loader/archive/JarFileArchive$AbstractIterator.class
org/springframework/boot/loader/archive/JarFileArchive$EntryIterator.class
org/springframework/boot/loader/archive/JarFileArchive$JarFileEntry.class
org/springframework/boot/loader/archive/JarFileArchive$NestedArchiveIterator.class
org/springframework/boot/loader/archive/JarFileArchive.class
org/springframework/boot/loader/data/
org/springframework/boot/loader/data/RandomAccessData.class
org/springframework/boot/loader/data/RandomAccessDataFile$1.class
org/springframework/boot/loader/data/RandomAccessDataFile$DataInputStream.class
org/springframework/boot/loader/data/RandomAccessDataFile$FileAccess.class
org/springframework/boot/loader/data/RandomAccessDataFile.class
org/springframework/boot/loader/jar/
org/springframework/boot/loader/jar/AbstractJarFile$JarFileType.class
org/springframework/boot/loader/jar/AbstractJarFile.class
org/springframework/boot/loader/jar/AsciiBytes.class
org/springframework/boot/loader/jar/Bytes.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$1.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$Zip64End.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord$Zip64Locator.class
org/springframework/boot/loader/jar/CentralDirectoryEndRecord.class
org/springframework/boot/loader/jar/CentralDirectoryFileHeader.class
org/springframework/boot/loader/jar/CentralDirectoryParser.class
org/springframework/boot/loader/jar/CentralDirectoryVisitor.class
org/springframework/boot/loader/jar/FileHeader.class
org/springframework/boot/loader/jar/Handler.class
org/springframework/boot/loader/jar/JarEntry.class
org/springframework/boot/loader/jar/JarEntryCertification.class
org/springframework/boot/loader/jar/JarEntryFilter.class
org/springframework/boot/loader/jar/JarFile$1.class
org/springframework/boot/loader/jar/JarFile$JarEntryEnumeration.class
org/springframework/boot/loader/jar/JarFile.class
org/springframework/boot/loader/jar/JarFileEntries$1.class
org/springframework/boot/loader/jar/JarFileEntries$EntryIterator.class
org/springframework/boot/loader/jar/JarFileEntries.class
org/springframework/boot/loader/jar/JarFileWrapper.class
org/springframework/boot/loader/jar/JarURLConnection$1.class
org/springframework/boot/loader/jar/JarURLConnection$JarEntryName.class
org/springframework/boot/loader/jar/JarURLConnection.class
org/springframework/boot/loader/jar/StringSequence.class
org/springframework/boot/loader/jar/ZipInflaterInputStream.class
org/springframework/boot/loader/jarmode/
org/springframework/boot/loader/jarmode/JarMode.class
org/springframework/boot/loader/jarmode/JarModeLauncher.class
org/springframework/boot/loader/jarmode/TestJarMode.class
org/springframework/boot/loader/util/
org/springframework/boot/loader/util/SystemPropertyUtils.class
BOOT-INF/
BOOT-INF/classes/
BOOT-INF/classes/com/
BOOT-INF/classes/com/rambo/
BOOT-INF/classes/com/rambo/service/
BOOT-INF/classes/application.yml
BOOT-INF/classes/com/rambo/service/ServiceApplication.class
META-INF/maven/
META-INF/maven/com.rambo/
META-INF/maven/com.rambo/example-service/
META-INF/maven/com.rambo/example-service/pom.xml
META-INF/maven/com.rambo/example-service/pom.properties
BOOT-INF/lib/
BOOT-INF/lib/example-common-V1.0.0.1.jar
BOOT-INF/lib/lombok-1.18.20.jar
BOOT-INF/lib/spring-boot-jarmode-layertools-2.5.2.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx
~/WorkSpace/example/example-service/target/
- 从上述结果可以看出,在
example-service
打包出来的依赖来看,BOOT-INF/lib/example-common-V1.0.0.1.jar
已被包含在JAR
包中了,所以直接通过java -jar example-service.jar
是可以直接运行的 - 关于
fat jar
的结构和运行方式,请期待下一篇文章《Spring Boot 代理启动 —— JarLauncher & WarLauncher》
的讲述。