简介本资源是一份面向Java开发者与Maven初学者的实战型学习资料聚焦Maven核心机制中的仓库管理、本地依赖引入及可执行JAR包构建三大痛点问题。内容覆盖本地/远程/中央仓库的定位与协同逻辑手把手演示本地JAR包手动安装与POM声明方法并对比详解maven-jar-plugin、maven-assembly-plugin和maven-shade-plugin三种打包方案的适用场景与配置要点。压缩包共6个文件5KB含2个Eclipse偏好设置文件.prefs用于IDE环境适配、1个关键pom.xml配置模板、1个示例Java主类、1个.classpath工程路径配置及1个.project项目元信息文件结构精简开箱即用。目前已有2772人学习下载适合需要快速理解Maven底层依赖流转、解决本地包引用异常或生成独立运行jar的中初级Java工程师。1. Maven 可执行 JAR为什么mvn package出来的 jar 双击就报“no main manifest attribute”你写完一个 Spring Boot 之外的纯 Java CLI 工具mvn clean package一跑target 目录下赫然躺着my-tool-1.0.0.jar——双击闪退。命令行敲java -jar my-tool-1.0.0.jar控制台冷冰冰甩出一句no main manifest attribute, in my-tool-1.0.0.jar。这不是配置漏了也不是 IDE 抽风而是 Maven 默认压根不往 MANIFEST.MF 里写 Main-Class。它把「可执行」这件事交给了你——但不是靠手改 MANIFEST而是靠理解 Maven 的坐标体系、依赖解析机制、以及三种打包策略背后的设计契约。这份资源不是教你怎么点几下鼠标生成 jar而是带你钻进maven-jar-plugin的archive配置、maven-shade-plugin的类重定位逻辑、甚至spring-boot-maven-plugin的LaunchScript注入时机。搞懂它你才能在 CI 流水线里稳稳输出带完整依赖入口点的 fat jar而不是每次上线前临时jar -uf手动补 MANIFEST——那才是真正的玄学运维。2. Maven 仓库的本质不是文件夹而是坐标寻址协议Maven 仓库常被误认为是“一堆 jar 包堆成的本地文件夹”但这种理解会直接导致mvn dependency:copy-dependencies失败时抓瞎。真实情况是Maven 仓库是一套基于 GAV 坐标GroupId:ArtifactId:Version的 HTTP 可寻址命名空间。它不关心物理路径只认坐标而本地.m2/repository只是这套协议的一个缓存实现。2.1 坐标如何映射到文件路径——从org.springframework:spring-core:5.3.31到~/.m2/repository/org/springframework/spring-core/5.3.31/spring-core-5.3.31.jarMaven 将 GAV 坐标按规则转换为路径这是所有插件行为的基础GroupId: org.springframework → 路径分段org/springframework ArtifactId: spring-core → 路径分段spring-core Version: 5.3.31 → 路径分段5.3.31 最终 jar 名spring-core-5.3.31.jar这个映射不是约定俗成而是硬编码在org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout类中。你可以验证删掉本地~/.m2/repository/org/springframework/spring-core/5.3.31/执行mvn compileMaven 会自动从中央仓库或你配置的镜像下载并严格按此路径结构存放。关键点在于Maven 从不扫描文件夹找 jar它永远先拼路径再检查该路径下是否存在*.jar和*.pom。2.2 本地包引入的三种合法姿势为什么system范围是反模式当你有一个公司内部未发布到 Nexus 的legacy-util-2.1.jar常见错误是写dependency groupIdcom.example/groupId artifactIdlegacy-util/artifactId version2.1/version scopesystem/scope systemPath${project.basedir}/lib/legacy-util-2.1.jar/systemPath /dependency注意systemscope 是 Maven 设计上明确标记为「不推荐用于生产」的机制。它绕过整个仓库解析链不参与依赖传递且mvn deploy时完全忽略——CI 构建必然失败。真正合规的引入方式只有三种✅ 方式一安装到本地仓库适合单机开发验证mvn install:install-file \ -Dfilelegacy-util-2.1.jar \ -DgroupIdcom.example \ -DartifactIdlegacy-util \ -Dversion2.1 \ -Dpackagingjar执行后jar 被按 GAV 规则写入~/.m2/repository/com/example/legacy-util/2.1/后续mvn compile会像引用任何远程依赖一样解析它。✅ 方式二部署到私有仓库Nexus/Artifactory团队标准流程mvn deploy:deploy-file \ -Dfilelegacy-util-2.1.jar \ -DgroupIdcom.example \ -DartifactIdlegacy-util \ -Dversion2.1 \ -Dpackagingjar \ -DrepositoryIdnexus-releases \ -Durlhttp://nexus.internal/repository/maven-releases/这要求settings.xml中已配置nexus-releases对应的认证信息。部署后所有团队成员只需声明坐标即可无需共享 jar 文件。✅ 方式三使用maven-install-plugin在构建生命周期内安装适合多模块项目中跨模块依赖在父 POM 的buildplugins中添加plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-install-plugin/artifactId version3.1.1/version executions execution idinstall-legacy-jar/id phaseinitialize/phase goalsgoalinstall-file/goal/goals configuration file${project.basedir}/lib/legacy-util-2.1.jar/file groupIdcom.example/groupId artifactIdlegacy-util/artifactId version2.1/version packagingjar/packaging /configuration /execution /executions /plugin这样每次mvn clean compile都会先将本地 jar 安装到本地仓库再进入编译阶段。它比手动install:install-file更可靠且可被 CI 复现。2.3 仓库镜像与settings.xml的优先级链为什么改了pom.xml的repositories还连不上私有源Maven 解析依赖时遵循严格的仓库优先级顺序项目 POM 中repositories声明的仓库仅对当前项目生效settings.xml中profiles激活的repositories用户级影响所有项目settings.xml中mirrors配置的镜像覆盖所有仓库请求最高优先级Maven 内置中央仓库Central兜底最典型的翻车场景你在pom.xml里加了公司 Nexus 地址repositories repository idcompany-nexus/id urlhttp://nexus.internal/repository/maven-public//url /repository /repositories但settings.xml里同时存在mirrors mirror idnexus-mirror/id mirrorOf*/mirrorOf urlhttps://repo1.maven.org/maven2//url /mirror /mirrors此时mirrorOf*/mirrorOf表示「所有仓库请求都走这个镜像」你的company-nexus请求会被强制重定向到repo1.maven.org自然 404。解决方法只有两个要么删掉mirrorOf*改为精确匹配如mirrorOfcompany-nexus/mirrorOf要么在settings.xml的 profile 中用repositories显式声明公司仓库并通过activeProfiles激活它——镜像不会覆盖 profile 中声明的仓库。3. 打可执行 JAR 的三种正统方案从 MANIFEST 到 ClassLoader 重写Maven 默认jar插件只打「原始 jar」仅含编译后的 class 文件和MANIFEST.MF无 Main-Class。要让它可执行必须注入启动元数据或合并依赖。主流方案有且仅有三个各自解决不同场景混用必踩坑。3.1maven-jar-pluginmaven-dependency-plugin分离式可执行适合依赖稳定、环境可控这是最轻量、最符合 Unix 哲学的方案主 jar 不含依赖依赖单独解压到lib/目录通过Class-Path指向它们。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.3.0/version configuration archive manifest addClasspathtrue/addClasspath classpathPrefixlib//classpathPrefix mainClasscom.example.Main/mainClass /manifest /archive /configuration /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-dependency-plugin/artifactId version3.6.0/version executions execution idcopy-dependencies/id phasepackage/phase goalsgoalcopy-dependencies/goal/goals configuration outputDirectory${project.build.directory}/lib/outputDirectory overWriteReleasesfalse/overWriteReleases overWriteSnapshotsfalse/overWriteSnapshots /configuration /execution /executions /plugin执行mvn package后target/下生成my-app-1.0.0.jar含MANIFEST.MF其中Class-Path: lib/guava-32.1.3-jre.jar lib/logback-classic-1.4.14.jar ...target/lib/目录含所有 runtime 依赖 jar机制本质JVM 启动时读取MANIFEST.MF的Class-Path将指定路径下的 jar 全部加入 classpath。maven-jar-plugin的addClasspathtrue/addClasspath会自动扫描target/lib/下的文件名并写入Class-Path行注意它不校验文件是否存在只拼字符串。提示classpathPrefix必须与maven-dependency-plugin的outputDirectory相对路径一致。若你把依赖拷到target/deps/这里就必须写deps/否则运行时java -jar找不到依赖。3.2maven-shade-pluginFat Jar全量合并适合分发给终端用户当用户环境无法保证依赖 jar 存在比如发给客户一个独立工具必须把所有 class 打进一个 jar。shade插件不是简单复制而是重写字节码中的类引用路径解决类冲突。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goalsgoalshade/goal/goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.Main/mainClass /transformer /transformers filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin关键机制在transformersManifestResourceTransformer会在最终 jar 的META-INF/MANIFEST.MF中写入Main-Class而filters排除签名文件避免SecurityException。但更深层的是shade的类重定位Relocation能力。假设你依赖的guava和netty都用了com.google.common.base.Preconditions但版本不兼容。你可以将其中一个 relocaterelocations relocation patterncom.google.common/pattern shadedPatternshaded.com.google.common/shadedPattern /relocation /relocations此时shade会将guava-*.jar中所有com.google.common.*类复制到shaded/com/google/common/路径下重写所有引用这些类的字节码包括你自己的 class 和其他依赖的 class把new com.google.common.base.Preconditions()改成new shaded.com.google.common.base.Preconditions()。这就是为什么shade比assembly更安全——它不是文件级拼接而是 JVM 字节码级的语义重写。3.3spring-boot-maven-pluginSpring Boot 专用 Launcher适合 Spring 生态如果你的项目是 Spring Boot绝不要用shade或assembly打可执行 jar。Boot 的Launcher是一套完整的类加载隔离机制plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version3.2.0/version executions execution goalsgoalrepackage/goal/goals /execution /executions /plugin它生成的 jar 结构特殊my-boot-app-1.0.0.jar ├── BOOT-INF/ │ ├── classes/ ← 你的代码 │ └── lib/ ← 所有依赖 jar不展开 ├── META-INF/ │ └── MANIFEST.MF ← Main-Class: org.springframework.boot.loader.JarLauncher └── org/ └── springframework/ └── boot/ └── loader/ ← Boot 自己的类加载器实现JarLauncher的核心逻辑是不使用 JVM 默认的AppClassLoader而是用LaunchedURLClassLoader加载BOOT-INF/classes和BOOT-INF/lib/*.jar。它通过MANIFEST.MF中的Start-Class你自己的主类触发启动并确保 Spring 的ApplicationContext能正确扫描BOOT-INF/classes下的Component。血泪经验若你强行用shade打 Spring Boot 项目SpringBootApplication会找不到spring.factories因为shade默认不合并同名资源文件。必须显式配置ServicesResourceTransformer否则启动直接ClassNotFoundException。4. 避坑五条真实踩过的 Maven 打包雷区这些不是文档里的警告而是某开发者在凌晨三点排查 CI 失败时记下的日志。4.1 现象mvn package成功但java -jar target/app.jar报ClassNotFoundException: com.google.gson.Gson原因你用了maven-jar-plugin的addClasspath但maven-dependency-plugin拷贝依赖的 phase 是package而maven-jar-plugin的jargoal 默认绑定在packagephase 的更早时刻Maven 生命周期中jar在package的早期执行dependency:copy-dependencies在晚期。结果MANIFEST.MF生成时target/lib/还是空的Class-Path里什么都没写。解决显式将maven-jar-plugin绑定到prepare-packagephase确保它在dependency:copy-dependencies之后执行plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.3.0/version executions execution phaseprepare-package/phase !-- 关键 -- goalsgoaljar/goal/goals configuration.../configuration /execution /executions /plugin4.2 现象shade打出的 jar 运行时报NoSuchMethodError: org.slf4j.LoggerFactory.getLogger原因slf4j-api和slf4j-simple版本不匹配且shade默认不处理META-INF/services/org.slf4j.spi.SLF4JServiceProvider这类服务发现文件。多个依赖 jar 中的同名services文件被覆盖导致 SLF4J 找不到实现类。解决添加ServicesResourceTransformer它会合并所有META-INF/services/下的同名文件transformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/4.3 现象本地mvn clean package正常Jenkins 上构建失败报Could not find artifact com.example:legacy-util:jar:2.1原因你用了systemscope 引入本地 jar而 Jenkins slave 机器上根本不存在${project.basedir}/lib/legacy-util-2.1.jar。system依赖不参与远程仓库解析也不上传到 Nexus。解决立即废除systemscope改用maven-install-plugin在构建时安装见 2.2 方式三或推动该 jar 发布到私有仓库。4.4 现象spring-boot-maven-plugin打出的 jar用java -cp target/app.jar com.example.Main启动失败原因-cp强制使用 JVM 默认AppClassLoader它无法识别BOOT-INF/目录结构。Spring Boot 的 jar只能用java -jar启动因为-jar会触发java.util.jar.JarFile的特殊解析逻辑找到MANIFEST.MF中的Launcher类。解决永远用java -jar target/app.jar不要试图用-cp绕过 Launcher。4.5 现象maven-shade-plugin重定位后反射调用Class.forName(com.google.common.base.Preconditions)仍失败原因shade只重写编译期已知的字节码引用而Class.forName()是运行时动态加载字符串com.google.common.base.Preconditions不会被重写。shade无法修改字符串字面量。解决避免在重定位范围内使用硬编码类名的反射或改用ClassUtils.resolveClassName()Spring 提供它支持别名映射最彻底的是——不要重定位 JDK 或主流库只 relocate 冲突的第三方小众库。5. 设计取舍为什么 Maven 不默认打可执行 jar——从单一职责到构建可重现性Maven 的设计哲学第一条就是每个插件只做一件事并做到极致。maven-jar-plugin的唯一职责是「将编译输出打包成标准 jar 格式」而「可执行」涉及三个正交问题入口点声明MANIFEST、依赖管理classpath、类加载策略fat vs thin。把这些塞进一个插件只会让jargoal 变成黑匣子失去可组合性。我们来对比三种方案的取舍表维度maven-jar-plugindependencymaven-shade-pluginspring-boot-maven-plugin产物大小小主 jar 1MB依赖独立大所有 class 合并常 50MB大含 Boot Loader2MB启动速度快JVM 原生 classpath 加载慢需解压/内存映射所有 class中Launcher 需解析 BOOT-INF调试友好性高可单独替换target/lib/中某个 jar低所有 class 混在一起无法 hotswap中可 debugBOOT-INF/classes依赖冲突解决能力无靠 Maven 依赖调解失败则报错强relocation resource 合并强Boot 自定义 ClassLoader适用场景企业内网服务依赖由运维统一部署独立 CLI 工具发给客户一个 zipSpring Web 应用云原生部署我曾经负责一个跨部门的 SDK 项目初期用shade打包结果下游团队反馈“你们的 jar 里log4j-core版本把我们的log4j-api覆盖了日志全没了”。我们花了两天才定位到是shade的 relocation 规则没写全。从那以后我每次设计 SDK 发布流程都强制走三步用maven-jar-plugin打标准 jar确保符合 Java SE 规范用maven-source-plugin和maven-javadoc-plugin生成 source/javadoc jar让下游能 attach 源码用maven-deploy-plugin发布到 Nexus所有坐标可追溯mvn dependency:tree能精准定位冲突。可执行 jar那是下游应用的事。SDK 只提供原子能力不越界承担执行环境责任。Maven 的克制恰恰是它能在二十年后依然支撑百万级 Java 项目的底层原因——它不帮你做决定只给你清晰的契约和可验证的机制。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
大文件日志查看器:分块读取与索引机制解析 简介:LogViewPro中文版是一款面向系统管理员、开发人员及运维人员的超大文本文件查看与分析工具,主要解决几GB级日志文件加载缓慢、检索困难的问题。压缩包为zip格式,整体约1.54MB,解压后运行LogViewPro.exe即可使用,界… · 2026/11/5 4:09:32
亚马逊棋AI工程实战:规则引擎、搜索算法与评估函数全解析 简介:这份资料包围绕亚马逊棋(又称亚马逊女战士棋)展开,面向机器博弈方向的学习者、AI 算法开发者以及棋类游戏编程爱好者,提供多个版本的实现代码与可执行程序,可用于人机对弈体验、算法研究与教学演示。压… · 2026/11/5 4:08:32
AutoIt绿色版实战:Windows窗口自动化、编译exe与避坑指南 简介:AutoIt V3.3.6.1绿色汉化特别版是一份面向Windows自动化脚本初学者与运维、测试人员的免安装工具包,围绕BASIC风格脚本编写与GUI自动化展开,可用于模拟键鼠、窗口与控件操作、执行程序和读写注册表,解决重复性批处理与桌面流… · 2026/11/5 4:08:32
预期功能安全SOTIF:无故障车辆为何仍会撞人? 简介:面向智能网联汽车功能安全从业者、系统与软件工程师及高校相关专业学习者,这份资料提供预期功能安全(SOTIF)领域的核心标准文本,重点解决自动驾驶与辅助驾驶系统在无故障情况下因性能局限、感知偏差和算法约束而引… · 2026/11/5 8:33:14
智能驾驶中的鸟瞰视角(BEV)技术解析与应用实践 1. 鸟瞰视角在智能驾驶中的核心价值在智能驾驶系统中,鸟瞰视角(BEV,Birds Eye View)技术正逐渐成为环境感知的关键组成部分。与传统的前视或环视摄像头相比,鸟瞰视角通过将多个摄像头的图像拼接并投影到俯视平面&#… · 2026/11/5 8:33:14
vLLM分布式数据并行原理与调优实践 1. 项目背景与核心概念 在大模型推理领域,分布式并行技术是突破单卡显存限制的关键手段。最近在调试一个基于vLLM框架的分布式数据并行(DP)实现时,遇到了几个典型问题。这里记录下vLLM中DP并行的实现原理、常见问题及解决方案。 vLLM作为当前最流行的高… · 2026/11/5 8:33:14
MySQL内置函数实战技巧与性能优化指南 1. MySQL内置函数全面解析作为一名长期与MySQL打交道的开发者,我发现很多刚入门的同行对内置函数的理解仅限于表面用法。今天我将结合多年实战经验,深度剖析MySQL最常用的四类内置函数,带你掌握那些官方文档里没写的实用技巧。2. 日期函数实战… · 2026/11/5 8:33:14
0-1 BFS算法在水质检测最短路径问题中的应用 1. 问题背景与算法选型蓝桥杯竞赛中的"水质检测"问题是一个典型的图论最短路径问题。题目通常描述为:给定一个NM的网格地图,其中某些网格是污染源(障碍物),检测员需要从起点出发,找到到达目标点的… · 2026/11/5 8:33:14
高效会议互动设计:10个实战方案提升40%会议效率 1. 会议互动环节设计的核心价值会议冷场和跑题是职场中最常见的两大痛点。根据我过去五年为37家企业设计会议流程的经验,传统会议模式中平均每小时的无效时间高达18分钟。而精心设计的互动环节能将会效提升40%以上,这个数据来自我们对实施新会议模式的23… · 2026/11/5 8:32:13
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38