news 2026/10/21 5:58:34

Maven项目构建全攻略:从pom.xml到多模块踩坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Maven项目构建全攻略:从pom.xml到多模块踩坑指南

如果你用Maven建过项目,大概经历过这种场景:在IDE里点了几下Next,一个项目就生成了,写了两周代码,突然发现打出来的包跑不起来,要么缺依赖,要么编译版本不对,最后所有人都围着pom.xml来回改。我后来意识到,大部分人缺的不是点Next的能力,而是没弄明白Maven项目到底是怎么组织起来的。这篇内容我按自己的实操经验,完整讲一遍怎么用Maven建项目:从最底层的pom.xml开始,到目录结构、命令行构建、多模块拆分,再到我踩过的坑。适合刚接触Maven的开发者,也适合用了很久但一直依赖IDE生成项目的同学。

1. Maven项目到底在“建”什么

1.1 Maven不是代码生成器,是项目组织方式

很多人误以为Maven是个代码生成器,点一下能生成一套能跑的Java代码,其实它做的是三件事:描述项目、管理依赖、驱动构建。说白了,你用Maven建项目,真正得到的是一个可以反复重放的标准流程——不管谁来、在哪台机器上、什么时候执行,mvn clean package都能产出结构一致的产物。

这一点在团队协作里特别重要。以前没有Maven的时候,A同学把jar包放到web项目的lib目录里,B同学也往里面放,版本冲突了根本说不清是谁改的。Maven把依赖从“物理散落”变成“声明式集中管理”,项目里不再囤积jar包,只写一行依赖坐标,构建时自动去仓库拉取。项目在物理上变干净了,逻辑上却变完整了。

另外,Maven项目有一个非常明显的特征:它不依赖IDE。哪怕你用记事本写文件,只要目录结构正确、pom.xml正确,命令行里就能构建成功。IDE在里面只是起到编辑和展示的作用,不是项目存活的前提。搞清楚这一点,你后面排查构建问题的时候会少走很多弯路。

1.2 目录结构是约定,不是配置

一个标准的Maven项目,从根目录看下来通常是这样的:

demo ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ └── resources │ └── test │ ├── java │ └── resources └── target

src/main/java放业务代码,src/main/resources放配置文件,src/test/java放单元测试,src/test/resources放测试用的配置,target是构建输出目录,由Maven自动生成。src下的目录结构是Maven的约定,不是强制配置,所以你不需要在pom.xml里写“哪一个是源码目录”,只要按这个位置放,Maven就认。

看到这里有人会问:不按这个约定行不行?可以,你需要在build配置里手动指定sourceDirectory、testSourceDirectory,把所有路径重新指一遍。但我强烈建议不要这么做,因为约定优于配置的核心目的是降低沟通成本。一个新人接手项目,看到熟悉的结构就能立刻定位代码;如果每个模块都是自定义路径,光找代码就得花半天。我自己见过一个老项目把源码放在src/java2这种目录里,后来维护的时候所有人都很痛苦。

这里还有个细节:target目录是构建的临时产物区,整个目录都可以随时删掉。所以做版本管理的时候,target一定要忽略掉,否则提交到版本库里全是垃圾文件,合并代码时冲突能吵到你怀疑人生。后面第4章我会专门讲忽略规则。

1.3 生命周期决定了你执行mvn xxx时发生了什么

Maven有一张内置的构建生命周期,你可以把它理解成一条流水线,不同阶段代表构建的不同深度。最常用的三段是clean、default、site,日常开发打交道最多的是前两个。

default生命周期里有一系列阶段,按顺序执行:validate、compile、test、package、verify、install、deploy。注意这里的关键逻辑:执行后面的阶段,会先把前面的阶段跑一遍。你执行mvn package,它不会只打一个包,而是先validate,再compile,再test,最后package。这也解释了为什么很多新手执行mvn package时发现明明没写测试,却会跑测试——因为test阶段在package之前,是流程的一部分。如果暂时不想跑测试,用的是-DskipTests参数跳过测试用例执行,而不是绕过test阶段。

理解生命周期对你的日常使用影响很大。比如你只想看代码能不能编译,执行mvn compile就够了,没必要直接package;想知道测试是否通过,执行mvn test;要打包,执行mvn package。这些命令本质上是同一个生命周期不同阶段的入口。配合上插件体系,每个阶段都会绑定对应的插件执行动作,比如compile阶段绑定maven-compiler-plugin,test阶段绑定maven-surefire-plugin。后面所有报错,你都能在生命周期对应的插件里找到根因。

2. 最小骨架拆开看:pom.xml、目录、settings.xml

2.1 pom.xml的坐标就是你的项目身份证

pom.xml是Maven项目的核心,所有的项目信息都在这里。一个pom文件最基础的内容是坐标信息,包括groupId、artifactId、version,这三项组合起来就是项目的唯一标识。你可以类比成收货地址:groupId是省份加城市,artifactId是街道和门牌号,version是批次版号。同一个项目、不同版本,就是同一地址不同批次。

除了坐标,pom.xml里还经常出现这些标签,我整理了一个速查表:

标签作用补充说明
modelVersionPOM模型版本固定为4.0.0,不需要改
parent父POM用来自动继承父级的依赖和插件配置
packaging打包方式jar、war、pom,多模块父级常见pom
properties自定义属性通常用来集中管理版本号和编码参数
dependencies项目依赖列表这里写的依赖会被实际引入
dependencyManagement依赖版本管理只管理版本号,不直接引入依赖
build构建配置插件、资源目录、最终产物名等
modules聚合子模块多模块项目里声明包含哪些子模块

新手最容易混淆的是dependencies和dependencyManagement。简单说,dependencies是“我要用这些库”,dependencyManagement是“这些库的版本由我统一说了算,谁要用不许自己改版本”。这个机制在多模块项目里极其重要,第5章会展开讲。

还有一个容易被忽略的标签是packaging。新建单模块项目时通常不需要写,默认就是jar。但当你开始做父子模块结构时,父POM的packaging必须明确写成pom,否则Maven会按照默认的jar去处理父模块,构建直接报错。我见过很多次有人把父POM打包方式忘了改,然后整个多模块构建全部失败。

2.2 settings.xml和.m2仓库,越早管越好

Maven项目运行时会依赖一个本地仓库目录,也就是.m2/repository,默认放在用户目录下。所有从远程仓库下载到的依赖包都会缓存到这里。你第一次构建一个项目时,如果看到大量“Downloading”日志,下载完成之后第二次构建明显变快,就是本地仓库在起作用。

真正影响日常开发体验的不只是本地仓库,还有一个容易被忽略的文件:settings.xml,同样在.m2目录下。这个文件不属于某个项目,它属于当前用户的所有Maven项目。里面能配置的东西很多,最常用的有三个:本地仓库路径、远程镜像源、私服认证信息。

我见过很多团队,项目pom.xml写得没问题,但每个人在自己的机器上构建行为都不一样,最后查下来都是因为settings.xml里配了不同的私服地址或者镜像规则。所以如果你在一家公司里做Java开发,入职后第一件事就是确认公司统一分发的settings.xml,不要自己建一个空的。个人开发时也建议明确设置本地仓库路径,不要在C盘系统盘上累积一堆缓存,磁盘吃紧的时候你会回来感谢这个习惯。

2.3 一个最简pom.xml可以直接拿来改

空谈理论没用,下面是一个真正能构建的最小pom.xml,我直接贴出来:

<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> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency> </dependencies> </project>

maven.compiler.release指定编译用的Java版本,这里用17,你可以根据自己环境改成11、21等。project.build.sourceEncoding强制源码文件用UTF-8编码,避免在Windows上编译时因为系统默认字符集导致中文乱码。

很多教程里还会提到maven-compiler-plugin,因为早期Maven版本需要手动配source和target。现在新一点的Maven版本已经支持通过maven.compiler.release属性直接生效,省掉了插件配置。不过如果你的Maven版本比较老,这个属性不生效,编译时会报“无效的发行版本”之类错误,那时候才需要回到插件配置这条路。我自己的建议是:优先确保Maven版本足够新,再写这个属性。

3. 两种建法实测:命令脚手架和手写骨架

3.1 先试archetype生成,再决定要不要用它

Maven官方提供了一个archetype插件,作用是拉取一个模板项目来生成骨架。最常用的一条命令是这样的:

mvn archetype:generate -DgroupId=com.example -DartifactId=demo \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.1 \ -DinteractiveMode=false

执行之后,它会在当前目录下生成一个名为demo的目录,里面基本是空的源码文件加一个pom.xml。你可以直接打开看结构,然后基于它改。

但我实测下来,这条命令有两个让人不太舒服的地方。第一,archetype插件第一次运行时需要下载模板目录,网络环境不太好的话会卡很久,而且经常看起来像“卡死了”,实际上是下载catalog文件没输出任何进度。第二,生成的模板比较老,pom.xml里基本没配置编码和编译版本,而且会附带上一个有点多余的示例测试类,你大概率还是要删了重写。

所以我的建议是:不清楚Maven目录结构的人,可以用archetype生成一次,感受一下Maven标准项目长什么样;已经明白结构的人,完全没必要依赖模板,手写骨架反而更快。

3.2 手写骨架的完整步骤

我自己现在建项目,基本都是手写骨架,整个过程不超过三分钟。以创建一个名为demo的项目为例,操作如下:

第一步,创建目录:

mkdir -p demo/src/main/java/com/example/demo mkdir -p demo/src/main/resources mkdir -p demo/src/test/java/com/example/demo

如果你使用Windows,用资源管理器创建同样结构的目录也行,关键是层级不要错。com/example/demo是Java包名对应的目录层级,你可以换成自己的包名。

第二步,在demo目录下创建pom.xml,内容可以直接用上面第2.3节那个模板,把artifactId改成demo。

第三步,在src/main/java/com/example/demo下创建一个主类,比如App.java:

package com.example.demo; public class App { public static void main(String[] args) { System.out.println("demo project built"); } }

第四步,在src/test/java/com/example/demo下创建一个基本的测试类:

package com.example.demo; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; public class AppTest { @Test void shouldWork() { assertTrue(true); } }

第五步,回到demo目录,执行构建验证:

mvn clean package

如果一切正常,你会看到构建成功的日志,并且在target目录下生成一个demo-0.1.0-SNAPSHOT.jar文件。

这里有一个很多人没注意到的点:src/main/resources目录如果暂时没有文件,也要先创建出来。因为一些IDE或Maven插件对空目录的处理不一致,你后面前面没建这个目录,后面往里面放配置文件时可能不小心建错位置,直接建到src/main/java下面了。先把空目录立好,后面往里面放东西就是顺手的事。

3.3 第一次构建会看到什么

第一次执行mvn clean package,你会看到大量日志,其中最重要的是这几类:

第一类是以Downloading开头的日志,说明Maven正在下载依赖。这里下载的不仅是你pom.xml里声明的JUnit,还包括Maven执行生命周期所需要的插件,比如compiler插件、surefire插件、jar插件。很多人误以为只有依赖才需要下载,其实插件同样在远程仓库里,这也是为什么第一次构建特别慢。第二类是以Tests run:开头的测试日志,surefire插件会把测试类跑一遍。第三类是最后成包的日志,提示target下生成了什么文件。

第一次构建时常见一个报错:无法解析某个依赖或插件。大部分情况下,要么是网络无法访问远程仓库,要么是settings.xml里的镜像配置有问题。遇到这种报错,先看完整的错误信息,里面会给出缺失的groupId、artifactId、version,然后去本地仓库目录里看看对应路径下是不是真的没有这个文件。排查思路比背命令重要得多。

4. 建完项目,第一件事是配置这些

4.1 先把JDK编译版本锁死

项目建好之后,第一件事不是写业务代码,而是把构建环境的关键参数固定下来。最容易被忽略、破坏性也最大的是JDK版本。

假设你在本地用JDK 17开发,但同事的机器上默认JDK是8,他直接执行mvn compile,很可能编译报错,或者在未指定release参数的情况下,Maven默认用当前运行的JDK编译,结果产物的字节码版本是52(对应Java 8),和你预期的行为完全不一样。这种问题不会出现在pom.xml里,也不会出现在代码里,最后所有人都在各自的机器上排查半天。

解决办法就是一开始就声明编译版本,我目前最推荐这个组合:

<properties> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>

相比传统的source和target配置,release参数是更严格的约束。source和target只限制编译器的输入输出版本,但允许你引用当前JDK里更高版本的API;release则会禁止你使用超出指定版本的API。比如你配release=17,项目里不小心用了Java 21才有的新API,编译会直接报错,这能避免很多“我本地能跑,别人那里跑不了”的情况。

4.2 依赖镜像不配,后面会被下载折磨

Maven默认从官方中央仓库下载依赖,但不同网络环境对中央仓库的访问速度和稳定性差别很大。国内开发环境尤其明显,第一次下载依赖经常超时,而且Maven默认不会因为一次失败就切换,而是反复重试,所以一个项目卡在下载阶段半小时是常有的事。

处理方式是在settings.xml里配置一个镜像,把对中央仓库的请求代理到更快的镜像源。基本配置如下:

<settings> <mirrors> <mirror> <id>mirror</id> <name>mirror</name> <url>https://mirror.example.com/maven-public</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> </settings>

如果你在公司内网,url路径通常由公司构建管理员统一提供,直接找他们要一份配置就行。个人开发者的话,网上也有很多公共镜像源可以选,教你怎么配的文章不少,我就不具体推荐了。要提醒的是,mirrorOf的值决定了哪些仓库请求会被拦截,最常见的值是central,只镜像中央仓库。如果你还用了其他第三方仓库,需要把它们也加入mirrorOf,用逗号分隔,比如central,my-repo。

镜像配好之后,之前下载失败过的依赖可能已经在本地仓库里产生了.lastUpdated结尾的失败标记文件,下次构建时Maven看到这些文件可能会拒绝重新下载,需要手动删除对应目录才能恢复。这也是一个经典坑,后面第6章的表里我会再总结一次。

4.3 用Maven Wrapper统一环境

另一个值得在项目建立初期就做好的事,是加Maven Wrapper。它是一组脚本和一个小插件Jar包,作用是让项目里面固化一个Maven版本,别人不管本地装没装Maven、装的是什么版本,只要执行项目里的mvnw脚本,它就会自动下载指定版本的Maven来执行构建。

用法很简单,在项目根目录执行:

mvn wrapper:wrapper

执行完后,项目里会出现mvnw、mvnw.cmd和.mvn/wrapper/maven-wrapper.properties。从这以后,所有人都应该改用./mvnw clean package来构建,而不是直接敲mvn。这样一来,“我本地Maven版本不一样”就不再是争吵的理由了。

有人觉得团队里大家都装了Maven,不需要这个。但现实中我还真遇到过因为Maven版本差异导致的行为不一致:某个版本对依赖解析的规则更严格,旧版本能构建的依赖树在新版本里直接冲突报错。加Wrapper的成本很低,收益却很实在,尤其是在协作团队里,我建议所有项目都统一加上。

4.4 .gitignore和依赖边界,越早定越省事

如果是用Git管理代码,项目根目录的.gitignore最好和pom.xml同步创建。一个适合Maven项目的忽略规则很长,但最核心的其实是这几条:

target/ *.class *.jar *.war .idea/ *.iml .vscode/

.idea和.vscode是IDE各自的配置目录,不应该提交进版本库。有人觉得“我用的IDE配置传上去大家就用一样的了”,实际上IDE版本不同、插件不同,这些配置文件合并起来全是冲突。target目录更是绝对不能提交,它随时可以重新生成,提交进去只会让仓库越来越臃肿。

依赖边界指的是另一个层面的问题:项目最开始就要想清楚依赖怎么引入。我见过有些新项目为了省事,复用一个老项目的pom.xml,把里面几十个依赖全复制过来,结果项目本身只用到了其中三四个。这样做短期内是省事了,但后续升级依赖、排查冲突时会非常痛苦。好的做法是每引一个依赖都问三个问题:真的需要它吗?用哪个scope?版本是不是该让父POM统一管?一开始守住边界,后面就不用天天救火。

5. 从单模块到多模块的演进

5.1 什么时候拆模块,怎么拆

单模块项目本身没问题,但项目变大之后,拆模块是一个绕不开的话题。我比较看重的判断标准是:代码里是否存在能够独立编译、独立复用的部分。比如一个后台系统,客户端、服务端、公共工具类如果全部塞在一个模块里,任何一个模块的改动都要带动整个项目重新构建,测试隔离也做不到,长期维护非常痛苦。

我参与过一个模拟后台系统,刚开始就是一个模块,后来功能越来越多,所有代码耦合在一起,每次编译都要几十秒,更别提版本发布时一个改动可能带进来一堆不相关的变更。后来我们拆成四个模块:common模块放公共DTO和工具类,service模块放核心业务逻辑,web模块放接口和控制器,parent模块作为顶层父POM统一管理。子模块之间的依赖关系变成单向的:web依赖service,service依赖common。

拆模块的时候有个原则值得记住:依赖方向要清晰,尽量让底层模块不依赖上层模块。common模块不应该知道service的存在,service不应该知道web的存在。这样上层模块的变更不会传导到底层,底层模块的复用也才能真正成立。如果你所在的项目业务边界确实很模糊,拆的时候可以先保守一点,只把真正明确独立的部分拆出去,后续再逐步演进。

5.2 父POM的正确打开方式

多模块项目需要一个父POM来做统一管理。它的packaging必须写为pom,并且通过modules标签声明子模块。下面是我常用的父POM形态:

<project> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>project-parent</artifactId> <version>0.1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>project-common</module> <module>project-service</module> <module>project-web</module> </modules> <dependencyManagement> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>project-common</artifactId> <version>${project.version}</version> </dependency> </dependencies> </dependencyManagement> <properties> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>

子模块的pom.xml则通过parent标签指向父POM:

<project> <parent> <groupId>com.example</groupId> <artifactId>project-parent</artifactId> <version>0.1.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>project-service</artifactId> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>project-common</artifactId> </dependency> </dependencies> </project>

这里有一个关键细节:子模块依赖project-common时,不需要写version。因为父POM的dependencyManagement里已经声明了project-common的版本是${project.version},子模块里引用时只写groupId和artifactId,Maven会自动从父POM的管理表里找到版本号。这个机制保证了所有子模块依赖同一个版本,不会出现各自引用不同版本导致编译错位。

${project.version}这个占位符很实用,它表示当前项目的版本。父POM是0.1.0-SNAPSHOT,子模块继承后默认也是0.1.0-SNAPSHOT,所以在dependencyManagement里用这个占位符,可以让内部模块之间的依赖版本跟着整个项目走,升级版本时只需要改父POM一处。

5.3 多模块最容易踩的两个点

第一个坑是单独构建子模块时找不到父POM。在聚合项目的根目录执行mvn package,Maven会从modules列表构建所有模块;但如果子模块目录下单独执行mvn package,Maven需要先解析parent标签里的信息。默认情况下它会先尝试通过relativePath找到本地文件系统里的父pom.xml,所以只要子模块和父POM的相对路径没写错,就能正确解析。但如果你用IDE直接打开子模块目录,或者用文件系统把子模块复制到别的地方,relativePath失效,Maven就会去本地仓库找父POM的pom文件,找不到就报错。

最常见的解决方法是在父POM的目录下执行mvn install,把父POM安装到本地仓库,这样子模块单独构建时也能解析到父级。我在多模块项目里的习惯是:平常都在根目录构建,只有需要单独验证某个模块时才去子模块目录构建,并且保证父POM已经install过了。

第二个坑是内部模块依赖版本写死。我见过有同事在子模块里手写了<version>0.1.0-SNAPSHOT</version>,另一个子模块写的是0.2.0-SNAPSHOT,两边因为不同版本引用同一份代码,最终打出来的包行为不一致。这种问题不好排查,因为编译期可能不会报错,只有运行期才会暴露。解决思路就是前面说的,所有内部依赖都不写version,由父POM统一管理,从机制上杜绝这种错位。

6. 实测中反复出现的坑和现在的建项目习惯

6.1 常见问题速查表

我把这几年建项目和调试构建时遇到的高频问题整理成一个表,方便你对症下药:

现象根本原因解决方向
第一次构建卡在下载阶段不动网络无法访问中央仓库或镜像源配置镜像源,重点检查settings.xml的mirror配置
报错“Duplicate ... class”不同依赖传递引入了同一个类的多个版本用mvn dependency:tree检查依赖树,排除重复传递依赖
报错“invalid target release”编译版本和当前JDK不匹配检查maven.compiler.release,确认它不高于当前JDK版本
子模块找不到父POM父POM没install,或relativePath指向错误在父目录执行mvn install,并检查relativePath
修改代码后执行mvn没有生效时间戳问题常见于本地仓库中SNAPSHOT依赖先mvn clean再构建,必要时install刷新到本地仓库
依赖下载失败后反复失败本地仓库生成.lastUpdated失败标记删除对应目录下的失败标记文件后重新构建
程序运行时报NoClassDefFoundError而编译正常某个依赖被标记为provided或运行时没有一起打包检查依赖scope,确认运行时依赖范围正确

这些坑大多不是代码逻辑问题,而是构建配置和依赖解析问题,所以解决它们最有效的工具是mvn dependency:tree和mvn help:effective-pom。前者打印当前项目的完整依赖树,后者输出生效后的完整POM,包括所有继承来的配置。遇到诡异问题,先看这两份信息,比瞎猜快得多。

6.2 那个让我排查很久的隐蔽问题

我要单独说一个印象极深的排查过程。当时一个项目里,所有人在IDE里构建都正常,但在命令行里执行mvn package却会报测试失败,而且是偶发。这个现象非常诡异,因为代码一致、依赖一致、操作步骤也一致。

后来反复对比,发现是IDE内置的Maven版本和命令行里的Maven版本不一致。IDE默认带了一个版本较旧的Maven,测试插件在这个版本上对类加载顺序的处理逻辑不一样,导致某个测试在IDE里永远能过,在命令行里的新版本下就会随机失败。换成Maven Wrapper之后,两边用的Maven版本终于一模一样,问题立刻消失。

这个经历让我养成了一个习惯:遇到“环境相关”的诡异构建问题,第一个检查点就是版本一致性。不是代码写得不对,而是构建工具链的版本矩阵在悄悄影响结果。这也是我在第4章强调Maven Wrapper的原因——团队越大,工具版本越需要统一,否则连报错的信息都会对不上。

6.3 我现在建项目的一套固定流程

结合前面这些内容,我把现在的建项目流程分享出来,你可以直接抄作业:

第一步,先想清楚坐标。groupId用公司或组织的反域名,artifactId用项目短名,version默认0.1.0-SNAPSHOT。

第二步,手写pom.xml。一次性把编译版本、编码、基础设施配置好,不依赖模板生成。

第三步,搭目录。src/main/java、src/main/resources、src/test/java、src/test/resources全部建好,再创建.gitignore。

第四步,写一个最小化的主类和测试类,不做任何业务逻辑,先把构建链路跑通。

第五步,执行mvn clean package,确认在命令行下单模块能够完整构建。这步做完,项目骨架就算真正立住了。

第六步,再考虑是否要多模块拆分。如果需要,就搭父POM,把公共依赖管理、插件管理放到父级,子模块只保留自己的专属配置。

第七步,生成Maven Wrapper,把构建脚本固定下来,然后配好settings.xml里的镜像源。

整个流程走完,项目已经具备了“任何人拉下来就能构建”的基础条件。后面写业务代码之前,我会再补一个小的冒烟测试来验证启动链路,但那就属于具体框架的范畴了,这里不展开。

以我个人的经验,建项目最忌讳想一步到位,更忌讳直接复制一个老项目改名就上。花十分钟把骨架和基础配置搭扎实,后面几个月的效率都能受益。反而是那些一开始图快、跳过配置的项目,最后往往要在依赖冲突和环境问题上付出数倍的时间来还债。

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

从非结构化文本到技能图谱:构建技能分类与推荐引擎实践

如果你打过招聘软件&#xff0c;大概对“技能”这个词不会陌生。JD上写着“精通Java”“熟悉MySQL”“了解Linux”&#xff0c;你把自己的技能一填&#xff0c;系统开始给你推职位。但背后有一个很少有人讲的环节&#xff1a;这些技能是怎么被结构化的&#xff1f;怎么判断“精…

作者头像 李华
网站建设 2026/10/21 5:57:02

插件化知识工作流:采集、整理与输出的本地优先实践

很多做知识管理的人&#xff0c;工具换了一茬又一茬&#xff0c;笔记还是越记越散。我自己的体会是&#xff1a;问题不出在工具本身&#xff0c;而是我们太想用一个“大而全”的软件解决所有问题。后来我花了几个月的业余时间&#xff0c;围绕最耗时的三个动作——采集、整理、…

作者头像 李华
网站建设 2026/10/21 5:56:11

REA建模:状态是事件的投影,订单库存账务一致性设计

看到"REA"这三个字母&#xff0c;懂行的人会先想到资源、事件、主体&#xff08;Resource-Event-Agent&#xff09;&#xff0c;而不是某个框架或库名。这套模型最早是给会计信息系统做业务建模用的&#xff0c;但我发现它解决的不只是记账问题&#xff1a;电商、供应…

作者头像 李华
网站建设 2026/10/21 5:54:38

Qt/C++ 手撸 RTMP 拉流:从 Chunk 解包到 AVCC 转 AnnexB 的完整实现

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

作者头像 李华
网站建设 2026/10/21 5:54:35

加密流量分析实战指南:工具、特征工程与模型选型

1. 加密流量分析为什么成了必选题做了几年网络流量方向的工作&#xff0c;我最直观的感受是&#xff1a;互联网已经在事实上“全面加密化”了。从浏览器到App&#xff0c;从Web服务到数据库连接&#xff0c;默认就是加密传输。以前做协议分析&#xff0c;抓个包直接在载荷里搜关…

作者头像 李华
网站建设 2026/10/21 5:54:13

单机商城用户系统实战:SQLite存储与权限控制详解

最近翻到一个购物商城项目的源码&#xff0c;越看越觉得有意思。这个项目完全单机运行、不联网&#xff0c;所有数据存储都靠SQLite一个文件搞定&#xff0c;却能实现完整的用户注册登录、商品浏览、购物车、下单结算这些功能。很多人一听到“单机”“SQLite”&#xff0c;就觉…

作者头像 李华