
简介针对Maven从入门到打包的常见困惑这份资料以精简示例工程演示了Maven仓库体系的层级关系、本地jar包的引入方式以及通过maven-jar-plugin、maven-assembly-plugin、maven-shade-plugin三种插件生成可执行jar包的配置方法。资源共6个文件以Java源码、pom.xml和Eclipse工程配置类文件.classpath、.project、.settings为主压缩包仅5KB内容虽轻但可作为动手验证的完整小项目。已有2767人学习下载适合刚接触Maven、对依赖管理和可执行jar打包不够熟悉的Java开发初学者对照工程结构和配置逐行理解会比只看文档更直观。通过这个小而全的示例读者能快速理清本地仓库与远程仓库的作用差异并掌握不同打包插件在生成独立运行jar时的取舍为后续搭建真实项目构建流程打下基础。1. 先搞清楚Maven仓库到底是个什么玩意儿Maven这个工具说简单点就是一个“自动化构建管家”而它最核心的机制之一就是仓库。很多新手刚接触Maven时对着settings.xml和pom.xml一脸懵其实只要搞懂仓库这套体系后面很多问题都能迎刃而解。Maven仓库本质上就是一个“存放jar包、插件、构建产物”的目录。它解决了两个问题一是统一管理依赖二是重复利用已有的构建结果。打个比方你家里有一个工具箱本地仓库工具不够用的时候就去楼下五金店买中央仓库如果五金店没有你想要的特定品牌工具你可能要去一家指定代理商私有远程仓库那里拿。就这么简单。在这套机制里最常用到的就是三种仓库本地仓库你电脑上的一份缓存目录Maven把下载过的依赖都放在这里默认在~/.m2/repository。中央仓库Maven官方维护的公共仓库地址是repo.maven.apache.org全球开发者共享。私有/远程仓库公司内部自建的Nexus或Artifactory或者国内常用的阿里云镜像仓库maven.aliyun.com核心作用是加速下载和存放私有构件。理解了这三个角色你就知道Maven解析依赖时的顺序了先查本地仓库没有就去远程仓库/中央仓库下载到本地然后再引用。网上很多人问“Maven下载了jar包为什么还报错”或者“明明配置了依赖却拉不下来”十有八九是本地仓库和远程仓库的关系没理清。Maven仓库相关的配置主要在两个地方全局配置settings.xml在Maven安装目录的conf文件夹下和项目配置pom.xml。前者管全局比如镜像地址、本地仓库位置后者管单个项目比如依赖坐标、插件配置。明白这一点后再去看网上各种“Maven配置教程”思路会清晰很多。2. Maven仓库的核心机制坐标、路径与依赖解析2.1 坐标是仓库的“门牌号”Maven仓库里的每个构件都有一个唯一的坐标由groupId、artifactId、version三部分组成也就是常说的GAV坐标。下载什么版本、从哪个路径找完全由这个坐标决定。举个例子dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.83/version /dependency这个坐标对应的本地仓库路径是~/.m2/repository/com/alibaba/fastjson/1.2.83/发现规律没groupId的点和artifactId、version一起一层层对应到目录路径。所以当你手动往本地仓库塞jar包时千万别放错目录否则Maven按路径去找根本找不到。我遇到过不少同事直接把下载好的jar包往~/.m2/repository下面随便一丢然后配置了pom.xml里的依赖结果项目依然报“找不到依赖”。原因就是目录结构和groupId不匹配。所以当你需要手动干预本地仓库时第一件事就是想清楚坐标再按规律放置。2.2 依赖解析的完整链路Maven解析一个依赖的完整过程是读取pom.xml找出所有直接依赖项。检查本地仓库中是否存在对应的坐标路径存在就直接用。本地没有就去settings.xml配置的镜像仓库/远程仓库拉取。如果远程仓库也没有报错“找不到依赖”。这一段链路里最容易出问题的是第3步。公司内网环境、网络不稳定、镜像仓库地址失效都会导致下载失败。一个很实用的排查技巧是下载失败时看本地仓库里对应目录的.lastUpdated后缀文件。这是个隐藏坑很多项目无法构建就是因为这些残留文件导致Maven认为“已经尝试过下载但失败”不会再自动重试。遇到这种情况直接把那个依赖目录整个删除重新执行构建命令即可。2.3 本地仓库位置怎么改默认情况下本地仓库在用户目录下的.m2/repository但这在C盘空间紧张或需要多环境共享依赖时会很麻烦。修改方式很简单在settings.xml里找到localRepository标签localRepositoryD:/maven_repository/localRepository改完以后重启IDE或执行命令时加-s参数指定settings.xml路径确保当前环境用了这套配置。我个人一般会在IDEA的Maven设置面板里把User settings file显式指过去避免IDEA默认使用自带配置而和命令行行为不一致。3. 如何把本地包引入到Maven项目里这个需求太常见了。比如公司内部提供的一个SDK是jar文件但没有上传到私有仓库或者你下载了一个老旧的第三方包中央仓库根本搜不到。这个时候有几种方案我逐个说下优缺点。3.1 方案一本地仓库安装最干净最规范的方式是把本地jar包安装到本地Maven仓库这样pom.xml里正常声明依赖坐标就能用。首先把jar文件放到任意目录然后在同一目录下执行mvn install:install-file \ -Dfilemy-sdk-1.0.jar \ -DgroupIdcom.company \ -DartifactIdmy-sdk \ -Dversion1.0 \ -Dpackagingjar执行完就能在本地仓库看到com/company/my-sdk/1.0/目录。接着在项目pom.xml里正常写依赖dependency groupIdcom.company/groupId artifactIdmy-sdk/artifactId version1.0/version /dependency优点很多坐标清晰、不污染其他机制、团队其他成员复制你的坐标也能使用前提是他们也在本地执行一次install。这是我最推荐的方式尤其是需要长期依赖同一个SDK包的时候。注意这里groupId、artifactId、version可以自己定义但建议和公司统一规范一致不然其他人拿到坐标都不知道这是什么包。3.2 方案二system scope只能救急别上生产还有一种做法是在pom.xml里把依赖指向本地文件路径dependency groupIdcom.company/groupId artifactIdmy-sdk/artifactId version1.0/version scopesystem/scope systemPath${project.basedir}/lib/my-sdk-1.0.jar/systemPath /dependency这种方式的优点是简单直接但缺点同样明显打包时如果没配好插件这个jar不会被打进最终产物里。scopesystem的依赖在部署和依赖传递上容易出问题团队协作时别人拉下来没有这个lib目录构建直接失败。用一次还行长期维护成本高。我现在基本不用这个方案除非是临时验证某个包的功能。3.3 方案三私有仓库一劳永逸如果你的团队有Nexus或Artifactory最科学的做法是把jar包上传到私有仓库然后pom.xml里配置repositories指向私有仓库地址。这样团队所有成员都不需要手工安装本地包只要配好settings.xml就能正常拉取。私有仓库的搭建和管理今天不展开讲但对于长期项目来说这是一本万利的投入。如果你还在纠结“今天这个包给不了别人怎么办”可以从现在开始推动团队搭一个Nexus成本很低收益非常大。4. 多种方式打可执行jar包到底该怎么选“可执行jar包”指的是java -jar xxx.jar能直接跑起来的包。Maven打可执行jar包的思路本质上就两种把依赖打进去、或者指定Class-Path让JVM去找外部依赖。下面几种是实践中用得最多的方案。4.1 用maven-jar-plugin打“瘦包”maven-jar-plugin是Maven自带的打包插件默认情况下打进jar包的只有项目自己的target/classes内容不包含依赖。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.4.1/version configuration archive manifest mainClasscom.example.MainApplication/mainClass /manifest /archive /configuration /plugin打包后生成一个瘦jar但java -jar直接跑会报“ClassNotFoundException”因为依赖不在里面。你需要把依赖目录一并拷贝出来运行时用-cp指定。这种方式好处是包体积小、干净坏处是部署麻烦点。4.2 用maven-shade-plugin打“胖包”shade插件是目前最主流的“可执行jar”解决方案。它会把你项目的所有依赖jar全部解压后重新打包进同一个jar文件里形成一个超大但独立的可执行文件。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.5.3/version executions execution phasepackage/phase goals goalshade/goal /goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.MainApplication/mainClass /transformer /transformers /configuration /execution /executions /plugin配置完成后直接mvn package就会在target目录生成一个xxx-shaded.jar留意实际生成的jar包名这个包可以拿出去直接java -jar。shade插件有几个常见坑需要说明如果项目里用了META-INF/services文件比如SPI机制会因合并冲突导致服务发现异常。需要加ServicesResourceTransformertransformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/配置文件application.yml、log4j2.xml被覆盖。解决办法是把配置通过外部化方式提供或者把资源文件“排除”再额外打进classes。4.3 用maven-assembly-plugin打“可分发包”assembly插件能打出一个zip/tar.gz包里面包含可执行jar以及lib/目录下所有依赖。适合那种需要额外脚本比如start.sh、start.bat的分发方式。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.7.1/version configuration descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs archive manifest mainClasscom.example.MainApplication/mainClass /manifest /archive /configuration executions execution idmake-assembly/id phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin执行mvn package后target目录会生成xxx-jar-with-dependencies.jar效果和shade类似但细节上有差别assembly是把依赖jar“按原样”放在包内指定目录而不是解压后合并对某些特殊场景比如签名jar包更友好。不过assembly对META-INF的处理历史坑比较多导致很多项目后来转向shade。但如果你要的不是单个jar而是一个“发布目录结构”assembly更合适。4.4 用spring-boot-maven-plugin打Spring Boot专用包如果你的项目是Spring Boot那么上面的插件统统可以不用直接用Spring Boot官方插件plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin它会执行repackage把项目打成可执行jar里面的结构是BOOT-INF/classes、BOOT-INF/lib这种。普通jar的Class.getResource加载方式在Spring Boot可执行jar里会有些差异所以尽量不要在Spring Boot项目里用shade或assembly去覆盖打包逻辑容易出怪问题。4.5 插件选型对比总结插件产物类型依赖处理方式适合场景maven-jar-plugin瘦jar不打包依赖依赖由外部运行时提供配合脚本maven-shade-plugin胖jar解压合并依赖需要单个可执行文件、快速交付maven-assembly-plugin胖jar或压缩包依赖目录结构保留需要启动脚本、目录分发的场景spring-boot-maven-plugin可执行胖jar特殊BOOT-INF布局Spring Boot项目官方推荐5. 打包过程中最常遇到的几个坑5.1 本地包打进不进去这个和前面第3节直接相关。排查思路很简单先确认本地依赖是否安装成功查看本地仓库坐标目录是否存在。再确认pom.xml里的坐标是否完全一致。最后确认打包插件是否包含了所有依赖。如果你用maven-jar-plugin但没有配Class-Path或外部依赖那打包出来的东西一定跑不起来。之前有个同事在本地包上纠结了一下午最后发现是version写错了1.0和1.0.0就是两个坐标。5.2 打出来的jar包运行报“No main manifest attribute”这句话的意思是你没有指定主类。检查两个地方一是pom.xml里插件配置有没有写mainClass二是IDE里构建时是不是用了其他插件覆盖了你的manifest入口。还有一个容易忽略的点如果你的项目里有多个plugin同时参与package阶段后执行的插件可能会覆盖之前的manifest配置。这时候要看构建日志里每个插件的执行顺序或者用mvn help:effective-pom看最终的插件配置。5.3 配置文件被覆盖、资源文件加载异常我用shade插件踩过最大的坑是application.yml被依赖jar里的同名文件覆盖了导致启动后数据库连到不知名地址。解决办法filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters同时还需要把项目自己的资源文件额外加进来或者改成外部化配置。更稳妥的做法是将配置文件放在jar外部启动时用--spring.config.location指定外部路径。6. 写在最后的一些建议Maven这套体系用熟了之后你会发现它其实不复杂核心就是“仓库机制”加“插件机制”。仓库管依赖下载和共享插件管构建打包各种动作。这两个机制搞明白项目构建的成功率能提升一大半。我自己在实际项目里的做法是所有需要长期使用的本地包一律通过install-file装进本地仓库并同步上传私有仓库所有可执行交付物优先用shade插件打包所有Spring Boot项目坚决只用官方repackage插件。这套流程跑了几年虽然不能说100%没遇到问题但踩坑的概率明显低很多。最后再分享一个小技巧当你怀疑Maven行为异常时先用mvn -X启动Debug日志它会打印依赖解析过程、插件执行顺序和仓库访问细节。很多时候比网上搜半天解决方案更高效。本文还有配套的精品资源点击获取