一、场景描述
-
采用父子工程的模块化管理,可以解决模块版本和模块间依赖的大多数场景和问题
-
采用
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》
的讲述。