这是一篇发布于 2017 年的早期 Maven 教程。内容按当年的视角写就:Java 8 主流、Spring 4.x 末班车、m2eclipse 是 Eclipse 上的事实标准、
kaptcha还是常用的验证码 jar。命令、截图描述、路径写法都保留当年的风格——这正是把历史笔记迁移到新博客的价值。但原文写得太短——只覆盖了「仓库」和「生命周期」两小块。下面是我把这些「原文」嵌入完整教程后的全量版,POM 详解、依赖机制、插件、多模块、Profile、版本管理、命令大全、镜像加速、错误排查一并补齐,按当年发布的标准来说足够受用很多年。
另注:文中提到的 jcenter 公共仓库已于 2021 年 2 月关闭,新项目请使用 Maven Central。
一、Maven 解决了什么问题
在 Maven 出现之前,Java 项目的依赖管理是一场噩梦:
- 手动从官网下载 jar 包(
kaptcha、fastjson、commons-io……),存到lib/目录 - 团队成员拷来拷去,jar 版本很难统一
- jar 包之间还有传递依赖(
kaptcha依赖jedis等等),手动梳理等于徒手算账 - 编译、测试、打包、部署的命令分散在 Ant 脚本里,每换一个项目就要重读一遍
- 跨 IDE 协作(Eclipse / IntelliJ IDEA / NetBeans)时项目结构互不兼容
Maven 想要做的只有一件事:把以上所有问题标准化。
它通过一个叫 pom.xml(Project Object Model)的 XML 文件,声明一个项目「是什么、依赖什么、怎么构建、怎么打包」。Maven 解析这份声明,自动完成下载、编译、测试、打包、部署。
两个核心理念值得一开始就刻进脑子里:
- 约定优于配置(Convention Over Configuration):Maven 对项目目录结构有强约定(
src/main/java放主代码、src/test/java放测试代码、target/放构建产物)。你按约定来,几乎不用写配置。 - 一切皆插件:Maven 本身只做「解析 pom + 按生命周期调度」,具体编译、测试、打包都由插件完成。这套机制让你可以无侵入地扩展任何环节。
二、Maven 仓库体系
本节为原文整理(出自早期笔记,稍作补全)。
2.1 三类仓库
Maven 仓库分三类:
- 本地仓库(Local Repository):本机缓存,第一次执行 Maven 命令时自动创建,默认路径是
~/.m2/repository。 - 远程仓库(Remote Repository):又分三种:
- 中央仓库(Central):Maven 官方维护,地址
https://repo.maven.apache.org/maven2/,所有 pom 文件默认继承超级 POM(super POM),中央仓库地址就在超级 POM 里。 - 私服:局域网内搭建的特殊远程仓库(如 Nexus、Artifactory),Maven 构建时先从私服下载,私服找不到再访问外网,下载完成还会缓存到私服——团队共用一份缓存。
- 其它公共库:如
jcenter(2016 年仍流行,2022 年已下线)、Spring 自己的仓库、JBoss 仓库等。
- 中央仓库(Central):Maven 官方维护,地址
2.2 添加远程仓库
远程仓库可在两处配置:
pom.xml的<repositories>标签——只对当前项目有效~/.m2/settings.xml的<profiles>标签——对所有项目有效
settings.xml 配法:
<settings>
<profiles>
<profile>
<id>dev</id>
<repositories>
<repository>
<id>aliyun-public</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>dev</activeProfile> <!-- 默认激活的 profile id -->
</activeProfiles>
</settings>
2.3 镜像(Mirror)
镜像用 <mirrors> 配置,<mirrorOf> 指定覆盖的远程仓库 id——配 * 覆盖所有:
<mirrors>
<mirror>
<id>aliyun-mirror</id>
<mirrorOf>*</mirrorOf>
<name>Aliyun Maven Mirror</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
大陆开发强烈建议配镜像:当年
repo.maven.apache.org在大陆访问就慢得离谱,到今天也是。
2.4 仓库优先级与解析顺序
这是原文没讲、但极其重要的一点。Maven 解析一个依赖的顺序是:
- 本地仓库(命中直接返回)
- 本地 profile / settings.xml 中配置的远程仓库
- pom 中声明的远程仓库
- 中央仓库(兜底)
- 镜像会重定向某些仓库的请求(如把
central重定向到阿里云)
如果配置的多个远程仓库都包含同一个 jar,Maven 取配置在前面的那个(声明优先)。理解这一点能解释很多「明明中央仓库有,为什么下不下来」的怪事。
2.5 分发到远程仓库(distributionManagement)
要在团队内共享你自己打的 jar,可以在 pom.xml 中配置:
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>http://nexus.example.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>http://nexus.example.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
账号密码一般配在 settings.xml(pom.xml 会被提交到代码库,敏感信息绝不能进 pom):
<settings>
<servers>
<server>
<id>nexus-releases</id>
<username>deployer</username>
<password>some_password</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>deployer</username>
<password>some_password</password>
</server>
</servers>
</settings>
然后执行 mvn deploy,Maven 就会把当前构件(artifact)发布到对应仓库。releases 仓库只接受非 SNAPSHOT 版本,snapshots 仓库只接受 SNAPSHOT 版本。
2.6 将第三方 jar 发布到本地仓库
本节为原文整理。
kaptcha(当年常用的验证码 jar)等第三方 jar 不在 Maven 中央仓库,需要手动安装到本地仓库。
# 下载 kaptcha,将 kaptcha-{version}.jar 复制到比如 C 盘根目录
mvn install:install-file \
-Dfile=c:\kaptcha-{version}.jar \
-DgroupId=com.google.code \
-DartifactId=kaptcha \
-Dversion={version} \
-Dpackaging=jar
然后按参数在项目的 pom 中声明依赖:
<dependency>
<groupId>com.google.code</groupId>
<artifactId>kaptcha</artifactId>
<version>{version}</version>
</dependency>
install:install-file 本质是调用 maven-install-plugin 提供的 install-file goal(goal 机制下面会讲)。
2.7 清理未下载完的数据
本节为原文整理。
依赖下载中断后会在本地仓库留下一堆 *.lastUpdated 文件,下次 Maven 看到它会以为「这个依赖已经下过且失败了」,直接跳过。清理:
find . -name "*lastUpdated" | xargs rm -fr
或者 Windows PowerShell:
Get-ChildItem -Path ~/.m2/repository -Recurse -Filter "*.lastUpdated" | Remove-Item -Force
顺带说一句:Maven 3.8.1 之后已经能自动清理
lastUpdated文件了,但当年还是手动时代。
三、POM 详解
pom.xml(Project Object Model)是 Maven 项目的核心。一个最小可用的 pom 长这样:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId> <!-- 组织标识 -->
<artifactId>my-app</artifactId> <!-- 项目标识 -->
<version>1.0.0-SNAPSHOT</version> <!-- 版本 -->
<packaging>jar</packaging> <!-- 打包类型:jar/war/pom/ear -->
<name>My Application</name>
<description>A simple Maven project</description>
</project>
<groupId>、<artifactId>、<version> 三者合称 GAV 坐标,是 Maven 世界的「身份证号」,任何 jar 都能用这三个值唯一定位。
3.1 继承与超级 POM
所有 pom 默认继承 super POM(超级 POM)——它定义了默认目录结构、默认中央仓库、默认插件版本。你不写任何东西时 Maven 也能跑,全靠 super POM。
自己也可以定义父 POM:
<parent>
<groupId>com.example.parent</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
<relativePath>../parent-pom/pom.xml</relativePath>
</parent>
子 POM 会继承父 POM 的所有配置(依赖、插件、属性、构建配置),常用于「多模块项目」和「公司统一管理依赖版本」。
3.2 常用元素
| 元素 | 作用 |
|---|---|
<properties> | 自定义变量(最常用:<java.version>1.8</java.version>) |
<dependencies> | 项目依赖 |
<dependencyManagement> | 只锁定版本,不实际引入——子模块继承后无需再写版本号 |
<build> | 构建配置(插件、资源目录、finalName) |
<modules> | 聚合多模块(父 POM 用) |
<repositories> / <pluginRepositories> | 自定义远程仓库 / 插件仓库 |
<profiles> | 环境隔离配置 |
<reporting> | 报告生成(覆盖率、checkstyle) |
<licenses> / <organization> / <developers> | 项目元信息 |
3.3 properties 变量
<properties> 是「pom 内部的常量」:
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.13.RELEASE</spring.version>
</properties>
然后用 ${变量名} 引用:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
好处是改一处版本号就能全项目生效。dependencyManagement 就是这个机制的标准实践。
四、依赖管理(dependency)
4.1 基本依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
4.2 scope(依赖范围):Maven 最精妙的设计之一
<scope> 决定依赖在哪个阶段可见,以及是否被打入包内。原文没提,但这是 Maven 必学:
| scope | 编译 | 测试 | 运行 | 是否打入包 | 典型场景 |
|---|---|---|---|---|---|
| compile(默认) | ✅ | ✅ | ✅ | ✅ | spring-core、fastjson |
| test | ❌ | ✅ | ❌ | ❌ | junit、mockito |
| provided | ✅ | ✅ | ✅ | ❌ | servlet-api(容器提供)、jsp-api |
| runtime | ❌ | ✅ | ✅ | ✅ | JDBC 驱动、jstl(运行时才需要) |
| system | ✅ | ✅ | ✅ | ✅ | 不走仓库,本地 jar(不推荐) |
| import | — | — | — | — | 仅在 <dependencyManagement> 中用,导入 BOM |
记忆口诀:
- compile:全程陪伴
- test:测试专用
- provided:你写代码要用,运行时由别人(容器)提供
- runtime:你写代码不用,运行时要用
举例当年典型的 Web 项目:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope> <!-- Tomcat 自带,部署时不打进去 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope> <!-- 编译时用 JDBC 接口,运行时才加载驱动 -->
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope> <!-- 不会进入最终包 -->
</dependency>
4.3 依赖传递与冲突解决
A 依赖 B,B 依赖 C——A 项目里会自动得到 C(传递依赖)。问题是:
- B 依赖 C 1.0,D 也依赖 C 2.0——用哪个?
- C 又依赖 E 1.0,X 依赖 E 2.0——怎么办?
Maven 用两个原则解冲突:
- 最近优先(Nearest Wins):依赖树中深度最小的胜出。直接声明 > 一层传递 > 两层传递。
- 声明优先(First Declaration Wins):深度相同时,pom 中先声明的胜出。
实战调试武器:
mvn dependency:tree # 看完整依赖树
mvn dependency:tree -Dverbose # 看冲突来源
mvn dependency:tree -Dincludes=com.google.code:kaptcha # 只看某条路径
4.4 排除依赖(exclusions)
不希望引入传递依赖时显式排除:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.13.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
4.5 可选依赖(optional)
标记 optional=true 表示「我自己用,但你不应该传递给我下游」:
<dependency>
<groupId>com.example</groupId>
<artifactId>my-module</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
五、三套生命周期
本节为原文整理。
Maven 有三套相互独立的生命周期:clean、default、site。每个生命周期包含若干阶段(phase),阶段有顺序,后面的阶段依赖前面的阶段。
5.1 clean 生命周期:清理
| phase | 作用 |
|---|---|
| pre-clean | 清理前需要完成的工作 |
| clean | 清理上一次构建生成的文件(默认删除 target/) |
| post-clean | 清理后需要完成的工作 |
5.2 default 生命周期:构建
这是日常最常用的。重要的 phase:
| phase | 作用 |
|---|---|
| validate | 验证工程是否正确、所有需要的资源是否可用 |
| compile | 编译项目源代码(src/main/java → target/classes) |
| test | 使用合适的单元测试框架(默认 JUnit)运行测试 |
| package | 把已编译的代码打包成可发布的格式(jar/war) |
| integration-test | 如有需要,将包处理并发布到一个能进行集成测试的环境 |
| verify | 运行所有检查,验证包是否有效且达到质量标准 |
| install | 把包安装到 Maven 本地仓库,可被其他工程作为依赖使用 |
| deploy | 在集成或发布环境下执行,将最终包拷贝到远程仓库供其他开发者共享 |
常见命令 vs phase 关系:
mvn compile→ 执行到 compile 阶段mvn test→ 执行到 testmvn package→ 执行到 packagemvn install→ 执行到 installmvn deploy→ 执行到 deploy你写
mvn package,Maven 会从 validate 一直执行到 package,所有上游阶段自动跑。
5.3 site 生命周期:站点
| phase | 作用 |
|---|---|
| pre-site | 生成站点前的工作 |
| site | 生成项目站点文档 |
| post-site | 生成站点后的工作 |
| site-deploy | 将项目站点发布到服务器 |
mvn site 会在 target/site/ 输出一份 HTML 项目报告,描述项目结构、依赖、测试结果、JavaDoc 等。
六、插件与 Goal:Maven 的真正引擎
原文只讲了生命周期,没讲生命周期「如何被驱动」——这是 Maven 最重要的补完。
6.1 Phase vs Goal
- Phase(阶段):生命周期中的一个「节点」,有顺序,不可单独执行(要执行就执行到这一节点及之前所有)
- Goal(目标):插件暴露的一个具体功能单元,可单独执行
Maven 的所有「真正的工作」都是插件完成的。Phase 只是一个调度点——执行到某个 phase 时,Maven 查找绑定到这个 phase 的所有插件 goal,依次调用。
6.2 内置绑定
Maven 内置了一些默认绑定,所以你执行 mvn compile 时不需要任何配置:
| Phase | 默认绑定的 Goal |
|---|---|
| compile | compiler:compile |
| test-compile | compiler:testCompile |
| test | surefire:test |
| package | jar:jar(或 war:war) |
| install | install:install |
| deploy | deploy:deploy |
6.3 显式配置插件
大多数时候你需要指定插件版本或参数:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
6.4 必须知道的常用插件
| 插件 | 关键 Goal | 作用 |
|---|---|---|
maven-compiler-plugin | compile、testCompile | 编译 Java 源码 |
maven-surefire-plugin | test | 运行单元测试(默认绑定到 test 阶段) |
maven-jar-plugin | jar | 打 jar 包 |
maven-war-plugin | war | 打 war 包 |
maven-resources-plugin | resources、testResources | 复制资源文件 |
maven-dependency-plugin | tree、copy、copy-dependencies | 依赖分析、拷 jar 到指定目录 |
maven-shade-plugin | shade | 打「uber-jar」(含全部依赖的 fat jar) |
maven-clean-plugin | clean | 清理 target |
maven-deploy-plugin | deploy | 发布到远程仓库 |
maven-install-plugin | install、install-file | 安装到本地仓库 |
maven-site-plugin | site | 生成站点 |
6.5 uber-jar 实战(maven-shade-plugin)
当年要把 Spring Boot 之前的所有依赖打成一个 fat jar 部署,maven-shade-plugin 是首选:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</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>
执行 mvn package,产出的 jar 已经包含所有依赖,可直接 java -jar xxx.jar 运行。
七、多模块项目
当年(现在也是)稍微大一点的项目都是多模块。Maven 的多模块 = 一个父 POM 聚合若干子模块。
7.1 典型结构
parent-pom/
├── pom.xml <!-- 父 POM:packaging=pom -->
├── user-service/
│ ├── pom.xml <!-- 子模块:packaging=jar -->
│ └── src/...
├── order-service/
│ ├── pom.xml
│ └── src/...
└── web/
├── pom.xml <!-- 子模块:packaging=war -->
└── src/...
7.2 父 POM 配置
<groupId>com.example</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging> <!-- 关键:父 POM 不是 jar/war,是 pom -->
<modules>
<module>user-service</module>
<module>order-service</module>
<module>web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
7.3 子模块 POM
<parent>
<groupId>com.example</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<!-- 不写 version:从父 POM 的 dependencyManagement 继承 -->
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>order-service</artifactId>
<version>${project.version}</version>
<!-- 引用兄弟模块:version 用 ${project.version>,自动同步 -->
</dependency>
</dependencies>
7.4 常用命令
# 在父 POM 目录执行:构建全部模块
mvn clean install
# 只构建某个模块及其依赖
mvn -pl user-service -am clean install
# 只构建某个模块(不构建其依赖)
mvn -pl web clean package
-pl(project list)+ -am(also make dependencies)是多模块项目最常用的组合。
八、Profile:环境隔离
当年部署一个项目到 dev / test / prod 三套环境是基本操作。Profile 让 pom 的某些配置根据环境切换。
8.1 在 pom 中定义
<profiles>
<profile>
<id>dev</id>
<properties>
<env>jdbc:mysql://dev-db:3306/app</env>
</properties>
<activation>
<activeByDefault>true</activeByDefault> <!-- 默认激活 -->
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<env>jdbc:mysql://prod-db:3306/app</env>
</properties>
</profile>
</profiles>
激活:
mvn package -P dev # 命令行激活
mvn package -P prod
8.2 activation 触发条件
除了手动 -P,还可以让 Maven 自动激活:
<activation>
<jdk>1.8</jdk> <!-- 特定 JDK -->
<os>
<name>linux</name>
<family>unix</family>
</os> <!-- 特定 OS -->
<file>
<exists>/etc/prod-marker</exists>
</file> <!-- 文件存在 -->
<property>
<name>env</name>
<value>prod</value>
</property> <!-- -Denv=prod -->
</activation>
8.3 配合资源过滤做多环境配置
最实用的场景——一个 application.properties 里写 ${db.url},根据 profile 替换:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering> <!-- 开启过滤 -->
</resource>
</resources>
</build>
src/main/resources/application.properties:
db.url=${db.url}
app.name=MyApp
不同 profile 的 db.url 注入不同值,构建出的 jar 里就是不同环境的配置。
九、版本管理
9.1 SNAPSHOT vs RELEASE
- RELEASE(如
1.0.0):稳定版本。Maven 不会重复下载远程仓库已有的 RELEASE 包。 - SNAPSHOT(如
1.0.0-SNAPSHOT):开发中版本。Maven 每次构建都会去远程仓库检查更新(配合-U强制更新)。
约定:
1.0.0-SNAPSHOT→1.0.0→1.0.1→1.1.0→2.0.0
<version> 元素配 SNAPSHOT 后,mvn deploy 会被自动推到 <snapshotRepository>,RELEASE 则到 <repository>(参考 2.5 节)。
9.2 版本号约定
当年主流方案(Semantic Versioning):
- 主版本号(major):不兼容的 API 变更
- 次版本号(minor):向下兼容的新功能
- 修订号(patch):向下兼容的 bug 修复
- 预发布标识:
-alpha/-beta/-RC1/-SNAPSHOT/-RELEASE
十、Maven 命令大全
# ===== 构建生命周期 =====
mvn clean # 清理 target/
mvn compile # 编译主代码
mvn test-compile # 编译测试代码
mvn test # 运行单元测试
mvn package # 打包(jar/war)
mvn verify # 跑完所有检查
mvn install # 安装到本地仓库
mvn deploy # 发布到远程仓库
mvn site # 生成站点
mvn clean install # 组合:清理后重构建并安装
# ===== 依赖分析 =====
mvn dependency:list # 列出所有直接和传递依赖
mvn dependency:tree # 树形展示
mvn dependency:analyze # 哪些依赖声明了没用 / 用了没声明
mvn dependency:resolve # 解析所有依赖到本地仓库
mvn versions:display-dependency-updates # 检查可升级版本
# ===== 调试 =====
mvn -X # 开启 debug 日志
mvn -e # 出错时显示完整堆栈
mvn -U # 强制更新 SNAPSHOT
mvn -DskipTests # 跳过测试
mvn -Dmaven.test.skip=true # 跳过测试编译和执行
mvn -o # offline 模式(只用本地仓库)
mvn -pl 模块名 -am # 构建指定模块及其依赖
mvn -P profile1,profile2 # 激活多个 profile
# ===== 仓库 =====
mvn install:install-file -Dfile=xxx.jar -DgroupId=... -DartifactId=... -Dversion=... -Dpackaging=jar
mvn deploy:deploy-file -Dfile=xxx.jar ... # 直接发布到远程仓库
# ===== 插件调用 =====
mvn help:describe -Dplugin=xxx # 查看插件信息
mvn help:effective-pom # 查看当前项目的「有效 POM」(合并父 POM 后)
十一、错误排查
11.1 依赖下载失败
症状:Could not resolve dependencies for project ...
思路:
- 配镜像(参考 2.3 节)
- 清理
*.lastUpdated(参考 2.7 节) - 删
~/.m2/repository/...对应目录后重下 - 检查
<repository>的<releases>/<snapshots>是否启用
11.2 版本冲突
症状:NoSuchMethodError / ClassNotFoundException / LinkageError,且发生在运行时
思路:
mvn dependency:tree -Dverbose | grep -i '冲突的类名'
找到后用 <exclusion> 排除多余版本,或在 <dependencyManagement> 中强制锁定。
11.3 编译错误
症状:[ERROR] BUILD FAILURE + 编译报错
思路:
- 检查 JDK 版本(
mvn -v看 Maven 用的 JDK) - 检查
maven-compiler-plugin的source/target配置 - 看一下「编译错」是不是某个传递依赖爆出来的(升级或排除该依赖)
11.4 插件找不到
症状:No plugin found for prefix 'xxx'
思路:
- 检查
<pluginRepositories>是否配了 - 镜像是否覆盖了 plugin 仓库(
mirrorOf不能是*,!some-plugin-repo之类) - 直接写完整 GAV(
<groupId>:<artifactId>:<version>)而不是简写
十二、m2eclipse 集成
本节为原文整理。
m2eclipse 是 Eclipse 的 Maven 插件(2017 年 IntelliJ IDEA 已自带 Maven 支持,但 Eclipse 用户还很多,所以保留这一节)。
12.1 预置的 mvn 命令
在 Eclipse 中右键 Maven 项目或 pom.xml 文件 → Run As,可以看到预置的 mvn 命令列表(如 mvn test、mvn install 等)。
12.2 自定义 mvn 命令
在 Run As 菜单选择 Maven Build…,输入自定义命令如 mvn clean install,点 OK。
下次右键 → Run As → Maven Build 就能看到这条自定义命令。
当年还有用 MyEclipse 的,但 MyEclipse 的 Maven 集成体验比 Eclipse + m2eclipse 差很多,统一建议用后者。
十三、实战:30 分钟搭一个企业级多模块 Web 项目骨架
学完上面 12 节,用一个例子串起来。目标:一个父 POM + user-service(jar)+ web(war)。
13.1 目录结构
company-pom/
├── pom.xml
├── user-service/
│ ├── pom.xml
│ └── src/main/java/com/example/user/User.java
└── web/
├── pom.xml
└── src/main/webapp/WEB-INF/web.xml
13.2 父 POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>company-pom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.13.RELEASE</spring.version>
<junit.version>4.12</junit.version>
</properties>
<modules>
<module>user-service</module>
<module>web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
13.3 user-service 子模块
user-service/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>company-pom</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
user-service/src/main/java/com/example/user/User.java:
package com.example.user;
public class User {
private Long id;
private String name;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
13.4 web 子模块
web/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>company-pom</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>web</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>user-service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
13.5 构建
在 company-pom 目录执行:
mvn clean install
会按 user-service → web 的顺序构建,user-service 打成 jar 安装到本地仓库,web 模块再把这个 jar 拷进 WEB-INF/lib/ 打成 war。
访问 target/web.war:
cp web/target/web.war $TOMCAT_HOME/webapps/
$TOMCAT_HOME/bin/startup.sh
十四、写给 2017 年读完这篇的你
那年 Spring 4.x 还在收尾,Spring 5.0(基于 Java 8 + Reactor)即将发布;Jenkins + Maven 是事实上的 CI;Nexus 2.x 是大多数公司的私服;Tomcat 8 是 Web 容器主流;IDE 还是 Eclipse / IDEA 之争。
掌握这篇讲的东西,足够你在中等规模的 Java 项目里游刃有余:
- 本地仓库 + 镜像:依赖下得动
- POM + 依赖管理 + scope:写得出规范的 pom
- 生命周期 + 插件:理解 mvn 命令到底干了什么
- 多模块 + Profile:管理多环境、多模块项目
- 错误排查:遇到问题知道去哪查
Maven 的世界后续演化出 Gradle(在 Android 圈几乎一统)、SBT(在 Scala 圈)——但 Maven 仍然是 Java 企业级项目的事实标准,特别是和 Jenkins、Sonar、Nexus 一起构成的「传统企业 CI/CD 黄金组合」。
下一篇开始,会基于这个基础聊一些更深入的话题(多模块的依赖最佳实践、私服搭建、版本发布流程)。本篇先到这里。