news 2026/9/7 2:32:09

Maven仓库与打包全解析:从本地依赖到可执行jar的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Maven仓库与打包全解析:从本地依赖到可执行jar的实战指南

简介:面向 Java 开发者的 Maven 实践资源,紧密围绕仓库机制、本地 JAR 引入和可执行 JAR 打包三个核心主题展开。内容先比较本地仓库、远程仓库与中央仓库的定位和协作关系,再说明如何将本地 JAR 按 groupId/artifactId/version 坐标放入本地仓库对应目录,在 pom.xml 中声明依赖后执行 mvn install 完成引用;随后以配置示例分别讲解 maven-jar-plugin、maven-assembly-plugin、maven-shade-plugin 三种打包方式,并对比普通可运行 JAR、依赖集成型 fat JAR 和可重定位资源的 shaded JAR 在类冲突、体积、启动方式上的差异,也顺带说明 manifest 中 Main-Class 的设置与依赖路径约定,方便开发者按交付场景挑选。压缩包共 6 个文件,约 5KB,文件构成以 Maven 配置 XML、Java 示例源码和 Eclipse 工程描述/类路径/偏好设置为主,呈现一个最小化 Maven 工程骨架,导入 IDE 即可对照实践。已有 2767 人学习下载,适合 Maven 初学者和 Java 后端开发者在搭建项目、处理本地二方包或制作可执行交付物时参考,有助于缩短构建配置的试错过程。 如果你去翻各大技术论坛,会发现有关 Maven 的提问长期霸占着两个"经典痛点":一个是"我明明把 jar 包放进项目里了,为什么编译还是找不到类?",另一个是"打包出来的 jar 为什么双击运行不了?"。

这两个问题看起来风马牛不相及,但本质上都指向同一个根源——你并没有真正理解 Maven 的仓库机制和构建生命周期的打包逻辑。很多人从 IDE 里"导入本地 jar"开始学 Maven,结果被 IDE 的友好界面惯坏了,浑然不知底层发生了什么。等到脱离 IDE、用命令行一跑就原形毕露。

这篇文章我打算用实际踩坑的视角,把三件事一次讲透:Maven 仓库到底是怎么运作的、如何正确引入本地包、以及用不同方式打出能直接跑起来的可执行 jar 包。内容偏实操,适合刚把 Maven 跑通、但还没真正理解构建逻辑的同学,也适合被各种打包插件搞晕的老开发。

1. 先弄清楚 Maven 仓库的三层结构:本地仓库、中央仓库、私有源的协作关系

1.1 本地仓库不是你项目的 lib 目录,而是全局依赖缓存

很多新手会混淆"本地仓库"和"项目里的 lib 文件夹",这是第一个认知误区。Maven 的本地仓库默认在用户目录下的.m2/repository里,它本质上是你这台机器上所有 Maven 项目共享的依赖缓存库。也就是说,你 A 项目下载过的依赖,B 项目如果声明了同样坐标,就会直接复用本地仓库里的那份,不会再重复下载。

实际工作中,我是强烈不建议改默认位置的,除非你的 C 盘真的吃紧。真要改,就去改settings.xml里的localRepository标签,比如这样:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <localRepository>D:/maven_repo</localRepository> </settings>

但你得想清楚一个后果:IDE 里配置的 Maven 如果指向不同的settings.xml,或者你命令行用的 Maven 和 IDE 内置的 Maven 版本不一致,它们各自维护的本地仓库可能就是两套。很多"换台电脑/换个人跑项目就编译报错"的诡异问题,源头就在这里——本地仓库没有随项目走,它只是缓存。

1.2 中央仓库与镜像/私服的本质关系

中央仓库(Maven Central)是全世界开发者共享的依赖源头,但它部署在海外,国内直连下载速度感人。所以你会看到国内各大云厂商都提供了镜像仓库,比如阿里云仓库地址:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

这段配置要放在settings.xml里的<mirrors>节点下。mirrorOfcentral表示只对中央仓库生效,如果你配了私服(比如 Nexus、Artifactory),mirrorOf里的值就值得仔细斟酌了:*表示所有仓库请求都走这个镜像,external:*表示只有外部仓库才走镜像、本地私服请求不受影响。如果没有私服,配*问题不大;如果有私服还配*,大概率会出现"私服上独有的包拉不下来"的问题。

这里多说一句关于私服本身的逻辑。私服存在的意义不只是内网加速,更重要的是团队内部发布的二方包(比如公司自研的公共工具库)存放地。配置私服需要在pom.xml里增加<repositories>标签:

<repositories> <repository> <id>nexus</id> <url>http://你的私服地址/repository/maven-public/</url> </repository> </repositories>

注意:pom.xml里配置的仓库只对当前项目生效,而settings.xml里的镜像是对全局生效的。两者的优先级关系也常把人绕晕——镜像会拦截仓库请求,而不是替代仓库配置。我见过不少团队把<repository>写到 settings.xml 里,结果项目换到别人机器上就废了,就是没分清这两者的作用域。

1.3 依赖坐标是仓库里的"门牌号"

Maven 仓库里的每个依赖都由groupIdartifactIdversion三个坐标唯一定位,缺一个都可能导致拉取失败。这三个坐标的含义很多教程都讲过,但很少有人解释它和文件的映射关系:org.example:demo:1.0.0在仓库里对应的路径是org/example/demo/1.0.0/demo-1.0.0.jar,groupId 里的点号会变成目录层级,这个规律在排查"明明坐标没问题但包找不到"时非常有用。

另外还有两个容易忽略的细节:

  • 同一个groupId:artifactId可以有多个版本,本地仓库里会并存各自版本的目录,不会互相覆盖。
  • Maven 解析版本冲突时遵循"最短路径优先"原则,路径相同则先声明者优先。看到这里你应该明白,依赖冲突排查(经常需要敲mvn dependency:tree命令)和仓库的目录结构强相关。

2. 把本地 jar 包"塞"进项目的几种姿势与坑

2.1 最不推荐的姿势:直接把 jar 扔进项目 lib 目录

先说我见过最多、也最容易翻车的方式:在项目根目录建一个lib文件夹,把本地 jar 放进去,然后在pom.xml里这样写:

<dependency> <groupId>com.local</groupId> <artifactId>some-lib</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/some-lib.jar</systemPath> </dependency>

这个方式当年的确能跑通,因为 IDE 会把这个 jar 加入编译 classpath。但现在 Maven 构建、CI/CD、命令行打包时,scope=system的依赖是默认不会被打进可执行 jar 的,很多"打包成 jar 后提示 ClassNotFoundException"的案例就是这里埋下的雷。它还是个"脏手段",污染了 pom 的可移植性——别人拿到你的项目,如果没有lib目录就废了。

我的建议是:除非你有极其特殊的原因(比如第三方 jar 加密无法入库),否则系统 scope 这条路能不走就不走。

2.2 标准做法:用 mvn install 把本地 jar 安装进本地仓库

正确且通用的做法是,先手动把本地 jar 安装到 Maven 本地仓库,然后再以正常依赖的方式声明。核心命令是:

mvn install:install-file \ -Dfile=/path/to/your/local.jar \ -DgroupId=com.local \ -DartifactId=some-lib \ -Dversion=1.0 \ -Dpackaging=jar

执行完之后,你就能在.m2/repository/com/local/some-lib/1.0/目录下看到some-lib-1.0.jar文件。此时在 pom.xml 里正常声明依赖即可,不需要任何 scope 和 systemPath:

<dependency> <groupId>com.local</groupId> <artifactId>some-lib</artifactId> <version>1.0</version> </dependency>

这里有一个很实用的小技巧:如果你的本地 jar 还带 sources 或 javadoc,可以把它们一起安装,方便在 IDE 里看源码。执行时追加-Dclassifiers=sources参数,格式为:

mvn install:install-file \ -Dfile=your-sources.jar \ -DgroupId=com.local \ -DartifactId=some-lib \ -Dversion=1.0 \ -Dclassifier=sources \ -Dpackaging=jar

2.3 团队协作场景:本地安装不够,要推到私有仓库

自己本机install只是"自嗨",因为其他同事的本地仓库里没有这个包,CI 服务器上也没有。只要你们有 Nexus 或 Artifactory,就应该一步到位,把本地 jar 推送到私服。

可以用mvn deploy:deploy-file命令实现:

mvn deploy:deploy-file \ -Dfile=/path/to/your/local.jar \ -DgroupId=com.local \ -DartifactId=some-lib \ -Dversion=1.0 \ -Dpackaging=jar \ -Durl=http://你的私服地址/repository/maven-releases/ \ -DrepositoryId=nexus

注意这里的repositoryId要和settings.xml里配置的<server>节点 id 对应上,否则私服会以匿名身份拒绝上传。很多报 401 权限错误的情况,都是这个 id 没对上。

如果公司没有条件架私有仓库,临时方案可以是把本地 jar 提交到 Git 仓库某个固定目录,再配合一个 Maven 插件(如maven-install-plugin的执行目标)在构建前先执行 install-file。但这是妥协方案,说实话不如直接引入一个轻量级的私有仓库来得省心。

2.4 引入本地包时的典型翻车现场

根据我的实战经验,以下几类报错最典型:

  • Cannot resolve symbol/程序包xxx不存在:IDE 能看到 jar 但 Maven 看不到,几乎都是因为依赖坐标或者版本和你 install 时不一致。
  • 打包后运行时ClassNotFoundExceptionscope=system的依赖没有被打进最终 jar。如果是打包插件配置了maven-shade-plugin的 artifactSet,还需要额外排除掉那些系统作用域的包。
  • Duplicate classes冲突:本地包和你引用的中央仓库某个依赖包含了相同的类路径,典型的是老版本日志框架冲突。这种要靠mvn dependency:tree去查重复依赖,再在 pom 里用<exclusions>排除。

3. 可执行 jar 包的本质问题:classpath 与主类入口

3.1 为什么 jar 包"能编译"但"不能运行"

很多人第一次敲java -jar xxx.jar得到"no main manifest attribute"报错时是懵的:明明项目里写了main方法啊。

原因在于:普通 jar 包只是一个 zip 压缩包,它内部有一个META-INF/MANIFEST.MF清单文件,这个文件里如果没有声明Main-Class属性,Java 运行时就不会知道该从哪个类开始执行。你能在 IDE 里右键"运行",那是 IDE 帮你指定了主类,它跟 jar 包的运行机制完全是两码事。

可执行 jar 需要同时满足两个条件:第一,MANIFEST.MF里有Main-Class指向入口类;第二,如果程序依赖了第三方库,还需要在 manifest 里声明Class-Path指向那些依赖,或者更高级的处理方式——把依赖类全部解压合并进一个"胖包"里。

3.2 普通 jar、可执行 jar、胖 jar、瘦 jar 这些概念

先把概念理清楚,后面选插件就不会乱:

  • 普通 jar:只包含项目自身的 class 文件和资源文件,不处理依赖。
  • 瘦 jar(Thin Jar):自身不包含依赖,靠 manifest 里的Class-Path指向外部依赖目录来运行。
  • 胖 jar(Fat Jar / Uber Jar):把项目自身的类、依赖的第三方类、配置文件全部合并到一个 jar 里,运行时不需要外部依赖也能直接运行。

每种形态都有适用场景。微服务项目的镜像构建一般不喜欢胖 jar,因为镜像分层不方便;但本地工具、演示项目、交给非技术人员运行的小工具,胖 jar 是体验最好的。

3.3 从"当前目录找依赖"到"把依赖装进 jar"的思路演变

以前老式做法是:打完 jar 后,手动在旁边建一个lib目录,把依赖全部复制进去,然后修改 manifest 里的Class-Path指向lib/xxx.jar。这在简单项目里可行,但在依赖树几十上百个 jar 的项目里就是灾难——你根本没法保证Class-Path的顺序和lib目录内容严格一致。

于是更聪明的方案出现了:与其人工维护外部lib目录,不如在打包阶段就把依赖直接塞进 jar 包。这样产物只有一个文件,分发、运行都干净利落。这也是 shade 和 assembly 插件成为主流方案的原因。

4. 三种常用打包插件对比:jar+classpath、shade、assembly

4.1 maven-jar-plugin:最朴素的方式,只打包自己的代码

先看最基本的配置:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.4.1</version> <configuration> <archive> <manifest> <mainClass>com.example.Main</mainClass> </manifest> </archive> </configuration> </plugin>

这段配置的效果是:打出来的 jar 里包含项目自己的类和资源,MANIFEST.MF中会多一行Main-Class: com.example.Main。但注意,它不会把任何第三方依赖打进去,运行时如果用到依赖仍然会报ClassNotFoundException

现实的用法是配合maven-dependency-plugin先把依赖复制到某个目录,比如:

<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>

这样target/lib下会生成所有依赖 jar,配合 jar 插件在 manifest 里声明Class-Path,最终也是可以运行的。但说实话,我现在基本不这么用了——配置冗长,还得保证lib目录跟着 jar 一起分发,出错的概率不低。

4.2 maven-shade-plugin:目前最主流的"胖 jar"方案

shade 插件我用的最多,它的核心能力是:将项目自身的 class 文件、第三方依赖的 class 文件全部解压后重新打进一个 jar 包,最终产物里不再依赖外部任何 jar。

最小配置如下:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.2</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.Main</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>

注意几个要点:

  • 插件默认会把依赖打进同一个 jar,不需要额外配置。
  • 如果多个依赖里有同名文件(比如META-INF/services下的 SPI 配置文件),shade 在合并时可能会互相覆盖,导致服务发现失效。此时需要用到ServicesResourceTransformer进行合并,配置里加一行<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>即可。
  • 依赖里如果包含签名文件(META-INF/*.SF*.RSA*.DSA),合并后运行时会报SecurityException: Invalid signature file digest,需要在 filters 中排除:
<filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters>

这个坑在引入 spring 相关库时特别常见,报错信息看似吓人,其实是签名文件冲突。

4.3 maven-assembly-plugin:既能打胖包,也能做分发包

assembly 插件的定位比 shade 更"杂",它不但可以把依赖全部打到一个 jar 里,还能生成 zip/tar.gz 格式的分发包,里面包含可执行脚本、配置文件、依赖目录等。如果你需要"一个压缩包 + 启动脚本"这种交付方式,assembly 是首选。

常见的"胖 jar"配置:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.7.1</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.example.Main</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>

执行mvn clean package后会在target目录下生成xxx-jar-with-dependencies.jar,这就是胖 jar。和 shade 相比,assembly 不太擅长处理META-INF/services文件去重和类冲突重定位,所以如果你的项目用了 SPI、或者有几个依赖里存在同路径类,shade 更加稳妥。

4.4 三者怎么选:一张表说清楚

对比维度maven-jar-pluginmaven-shade-pluginmaven-assembly-plugin
是否包含依赖不包含包含(合并类)可包含(合并类)
配置复杂度
依赖冲突处理不处理可排除,可重定位有限处理
SPI 服务合并不支持支持且必须用支持较弱
额外产物流配合复制依赖可做瘦包单 jar 为主可做 zip/tar.gz 分发包
典型场景被其他项目依赖的库Spring Boot 前的普通服务/工具命令行工具发布包

我的个人建议是:如果是纯工具类小项目、要交出去给别人直接java -jar运行,优先shade;如果是需要带启动脚本、配置文件、日志目录一起分发的服务,用assembly的 zip 模式;如果是给别的项目调用 API 的 SDK 类项目,用maven-jar-plugin打普通 jar 就够了,千万别打胖包。

5. Spring Boot 项目打包的特殊逻辑与常见误区

5.1 spring-boot-maven-plugin 的全量重打包机制

Spring 生态里打可执行 jar 时,通常不会去配上面那些插件,而是直接用spring-boot-maven-plugin。它的工作机制容易让人误解:Spring Boot 插件的repackage目标会接管package阶段,但它是"二次加工"——先让 Maven 用默认方式打出原始 jar,然后再把原始 jar 的内容重新整理,生成一个 Spring Boot 特有的"可执行胖 jar"。

我们平时执行mvn clean package看到类似repackage的日志,就是它在工作。最终产物里,项目自身的BOOT-INF/classes、依赖 jar 在BOOT-INF/lib,启动器类在org/springframework/boot/loader,整体目录结构和普通 Java 项目完全不同。这也是为什么你不能用java -jar target/original-xxx.jar去运行(那个只是中间产物)。

标准配置长这样:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.example.Application</mainClass> </configuration> </plugin>

5.2 关于 Spring Boot 打可执行 jar 的三个真相

第一个真相:Spring Boot 生成的可执行 jar 并不是把依赖类解压合并,而是把依赖 jar 原样嵌套在BOOT-INF/lib目录下,由org.springframework.boot.loader.JarLauncher在运行时动态加载。这就意味着,如果你试图用jar tf看到普通 Java 项目那种"所有 class 混在一个文件里"的结构,是看不到的——它本来就是另一种运行模型。

第二个真相:如果你的 Spring Boot 项目里引入了本地 jar(system scope),默认情况下 Spring Boot 的 repackage 也不会把 system scope 依赖包含进BOOT-INF/lib。所以即便 Spring Boot 项目也很容易出现"开发正常、打包后运行缺类"的问题。处理方式还是要先把本地 jar 正常 install 到本地仓库/私服,以 compile scope 引入。

第三个真相:Spring Boot 的Main-Class写在 manifest 里是org.springframework.boot.loader.JarLauncher,你自己的启动类在 manifest 的Start-Class属性里。所以如果你手动去改 manifest,改错成自己的main方法,反而会运行失败。记住这个差异,排查"为什么 java -jar 启动不了"时会很有帮助。

5.3 外部配置文件带出 jar 外的一种实践

可执行 jar 打好之后,很多人会遇到一个问题:配置在application.yml里,而配置是写进BOOT-INF/classes的,每次改配置都要重新打包。实战里更灵活的做法是:让程序优先读取 jar 包外部的config目录或者当前目录下的application.yml

放到 jar 的同级目录,再在启动脚本里指定:

java -jar app.jar --spring.config.location=config/application.yml

如果你希望不写命令行参数、自动检测外部配置文件,可以在 jar 包同级放一个config文件夹,Spring Boot 默认的配置搜索顺序本来就包含./config/。这个策略在交付给运维、免去频繁重打包的开发环境中非常实用。

6. 命令行打包实战:从 clean 到 deploy 的完整操作追踪

6.1 最常用的命令组合及其含义

日常开发和 CI 中最常敲的几条命令我列一下,并说明实际执行了什么:

mvn clean compile

清理target目录,然后编译源码到target/classes。这条命令不会打包,也不会跑测试,是快速检查代码能否编译通过的最好方式。

mvn clean package

在编译基础上执行测试,并把 jar/war 包生成到target。如果你配置了 shade/assembly/spring-boot 插件,可执行 jar 也是在这个阶段生成的。

mvn clean install

在 package 阶段产物基础上,把 jar 安装到本地仓库。这样其他本地项目可以直接以 Maven 依赖方式引用它。如果你改了公共模块,想让依赖方立即生效,就得执行 install。

mvn clean deploy

在 install 基础上,将 jar 推送到私服仓库。这里就会用到pom.xml里的<distributionManagement>节点,以及settings.xml里的服务器认证信息。

6.2 跳过测试的正确姿势,而不是"不管测试"

有些人急于打包,直接敲mvn package -DskipTests。这个参数的真实效果是:编译测试代码,但不执行测试。还有一个更彻底的叫-Dmaven.test.skip=true,它连测试代码都不编译。两者区别你要清楚,在 CI 流程里如果测试代码有编译错误但不想跑测试,-DskipTests也会挂掉,因为测试编译是独立于测试执行的。

我个人的习惯是:本地开发打临时包用-Dmaven.test.skip=true追求速度,提交 CI 时强制完整跑测试,绝不跳过。

6.3 命令行里临时指定参数:-D的高阶用法

链式的-D参数是 Maven 使用者最容易忽略的宝藏。比如你想在打包时临时避开某个私服不可用的问题,可以手动把仓库地址变成命令行参数:

mvn clean package -DaltDeploymentRepository=nexus::default::http://私服地址/repository/maven-releases/

又比如在构建时动态指定版本号:

mvn clean package -Drevision=2.0.0-SNAPSHOT

这要求 pom 里的<version>写成${revision}。这种动态版本号做法在多人分支并行开发时很能减少版本冲突。

7. 实战经验总结:我踩过的坑和给你的建议

7.1 关于本地依赖的三个建议

把 jar 包"放"进项目,说起来只是一句话,但它们之间的差异会直接影响后续构建和分发。我的三个建议是:

  1. 本地包优先走mvn install:install-file进本地仓库,别用systemPath,除非这个 jar 只是临时调试用。
  2. 如果本地包是给多人用的,尽快推送到私服,靠 GitHub 上放 jar 文件、或者靠"大家手动 install"都是隐患极大的方案。
  3. 把 jar 文件和来源说明放在项目文档里记录一下,甚至可以在 Git 里建一个third-party-jars/目录存原始 jar,下次有人问"这个 jar 哪来的"不至于查半天。

7.2 关于打包插件的三个建议

  1. 先看项目类型再选插件。Spring Boot 项目别画蛇添足去配 shade 和 assembly,spring-boot-maven-plugin 自带 repackage 已经足够。普通工具类、无框架项目、需要单个 jar 分发的,shade 优先。
  2. 用 shade 时,一上来就配好排除签名文件和ServicesResourceTransformer,不用等到出 bug 再加。这俩是我遇到概率最高的两个问题。
  3. 任何打包配置比拼的是最终java -jar能不能跑起来,不要停留在"构建成功"的幻觉里。构建成功只能说明编译和打包没报错,运行时的 classpath、资源文件合并、依赖缺失是另一套维度。

7.3 关于排查思路的三条链路

遇到打包问题,我推荐按照下面的顺序排查,比乱试要快得多:

  1. 问题表现在"编译失败",先查仓库:敲mvn dependency:tree -Dverbose,看具体依赖完整路径、版本和来源,确认坐标是否匹配、本地仓库有没有有效缓存文件。很多时候rm -rf ~/.m2/repository/相关目录后重新mvn install反而能解决所谓"缓存损坏"问题。
  2. 问题表现在"jar 运行找不到主类",先查 manifest:解压看META-INF/MANIFEST.MF,确认Main-Class写没写对,或者是否被 Spring Boot 的JarLauncher逻辑覆盖了。
  3. 问题表现在"运行时报 ClassNotFoundException",先看依赖有没有进 jar:用jar tf your.jar在 jar 里搜索报错的那个类路径,如果不存在就是依赖没打进去;如果存在,多半是重复类或 SPI 冲突。这一步往往能快速定位是 shade 的 artifactSet 配置问题还是 assembly 的 descriptorRef 问题。

打包可执行 jar 这件事,本质上就是在理解 Maven 仓库、生命周期和插件三者之间的关系。仓库决定了依赖从哪来,生命周期决定了构建步骤的顺序,插件决定了最终产物长什么样。把这三条线串起来,遇到任何打包问题你都不会再慌。至少对我来说,当年被scope=system坑过、被 shade 合并签名文件坑过之后,就再也不满足于"能跑就行"了——搞清楚底层逻辑,下次踩坑的概率才会真正降下来。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 2:31:49

PDF编辑与OCR识别:从扫描件到批量处理的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:31:20

全能Agent养成记:从Skills设计到腾讯云部署的最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:26:38

相控阵雷达原理与工程实践:从相位差到有源阵列

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:26:34

不会电脑也能轻松上手:云端进销存选型与使用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:26:26

100G FPGA板卡UDP协议栈移植实战:从回环到双向打流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华