外观
Maven 与私服:镜像、依赖冲突
Maven 的坑集中在两处:镜像配错导致下载慢/失败,以及依赖冲突导致运行时才报 NoSuchMethodError。
一、settings.xml
xml
<!-- ~/.m2/settings.xml -->
<settings>
<localRepository>/data/maven/repo</localRepository>
<mirrors>
<mirror>
<id>nexus</id>
<name>internal nexus</name>
<url>http://nexus.internal:8081/repository/maven-public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<servers>
<server>
<id>nexus-releases</id>
<username>deploy</username>
<password>{加密后的密码}</password>
</server>
</servers>
<profiles>
<profile>
<id>jdk17</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</profile>
</profiles>
</settings><mirrorOf> 的写法:
| 值 | 含义 |
|---|---|
central | 只代理 central |
* | 代理所有仓库(包括私服自己,会导致死循环) |
external:* | 所有外部仓库,但不代理 localhost 和内网 |
*,!nexus-releases | 全部代理,除了指定的 |
mirrorOf 写 * 且 url 指向私服
如果私服本身又去代理 central,某些配置下会形成请求回环,表现为下载卡死或 404。用 external:* 或显式排除。
另外:repositories 里配置的仓库也会被 mirror 拦截,所以只要 mirror 覆盖了 central,pom 里再加阿里云仓库是无效的。
密码加密:
bash
mvn --encrypt-master-password
mvn --encrypt-passwordtext
{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}把 master 密码放进 ~/.m2/settings-security.xml。
二、依赖冲突
现象
编译通过,运行时报:
text
java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(JLjava/lang/String;)V
java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder定位
bash
mvn dependency:tree -Dverbose -Dincludes=com.google.guava:guavatext
[INFO] com.example:order-api:jar:1.4.2
[INFO] +- com.example:common-utils:jar:1.0.0:compile
[INFO] | \- (com.google.guava:guava:jar:19.0:compile - omitted for conflict with 32.1.3-jre)
[INFO] \- com.example:cache-client:jar:2.1.0:compile
[INFO] \- com.google.guava:guava:jar:32.1.3-jre:compileMaven 的调解规则:
- 最短路径优先(层级浅的赢)。
- 路径深度相同时,先声明的赢。
dependencyManagement里的版本强制生效。
19.0 被 omitted for conflict,实际用的是 32.1.3-jre。如果代码按 19.0 的 API 写,就会 NoSuchMethodError。
解决
xml
<!-- 1. 排除传递依赖 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>common-utils</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>xml
<!-- 2. 统一版本(推荐) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
</dependencyManagement>bash
# 3. 看最终生效版本
mvn help:effective-pom | grep -A3 '<artifactId>guava</artifactId>'
# 4. 看整个依赖树
mvn dependency:tree -Dverbose > /tmp/tree.txtSpring Boot 项目用 BOM 统一版本:
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
</parent>BOM 之外要覆盖版本时,在 dependencyManagement 里显式声明。
三、常见冲突类型
| 现象 | 冲突对象 | 排查 |
|---|---|---|
NoClassDefFoundError: org/slf4j/... | 多个 slf4j 绑定 | dependency:tree -Dincludes=org.slf4j |
NoSuchMethodError | 同一库多版本 | -Dverbose 看 omitted |
ClassCastException 在 JDK 内部类 | 两个版本的同一 jar | 查 lib/ 下是否有重复 |
java.lang.LinkageError | 同名类被加载两次 | 查 fat jar 里的 META-INF |
UnsupportedClassVersionError | 编译 JDK 高于运行 JDK | 对齐 maven.compiler.release |
打出的 fat jar 里检查重复:
bash
unzip -l target/app.jar | grep -c 'BOOT-INF/lib/'
unzip -l target/app.jar | grep 'guava'四、私服 Nexus
bash
docker run -d --name nexus -p 8081:8081 \
-v /data/nexus:/nexus-data \
sonatype/nexus3:3.70.1bash
docker logs nexus | grep -i 'admin'text
-------------------------------------------------
Initial admin password is in /nexus-data/admin.password
-------------------------------------------------bash
cat /data/nexus/admin.passwordtext
8f2c1e9a-7b3d-4c5e-8a0b-2c4d6e8f0a1b三个仓库:
| 仓库 | 类型 | 用途 |
|---|---|---|
maven-releases | hosted | 正式版本 |
maven-snapshots | hosted | 快照 |
maven-public | group | 聚合上面两个 + 代理 central |
发布到私服:
xml
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>http://nexus.internal:8081/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>http://nexus.internal:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>bash
mvn deploy -DskipTestsreleases 版本不能覆盖
maven-releases 默认禁止重复发布同一版本,会报 400 Repository does not allow updating assets。
改版本号重发,或临时把仓库策略改成 "Allow redeploy"(不推荐,会破坏可追溯性)。
五、构建加速
bash
# 跳过测试
mvn -B clean package -DskipTests
# 只跑指定测试
mvn -B test -Dtest=OrderServiceTest
# 并行构建
mvn -B clean package -T 1C # 每核一个线程
# 离线
mvn -o clean package
# 只构建子模块及其依赖
mvn -pl order-api -am clean package-T 1C 在模块多的项目里能省一半时间,但并行构建对插件有要求,出问题就退回串行。
六、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 下载极慢/超时 | 没配镜像 | mirrorOf central 指向私服 |
mirrorOf * 后私服包拉不到 | 所有请求被劫持 | 改 external:* |
运行时 NoSuchMethodError | 依赖冲突 | dependency:tree -Dverbose |
| 本地能跑、服务器不行 | 本地仓库有旧版本 | 清 ~/.m2/repository 对应目录重下 |
SNAPSHOT 拿到旧包 | 本地缓存未更新 | mvn -U clean package |
| 打包缺资源文件 | 没配 <resources> | 检查 src/main/resources 与插件配置 |
| 时区导致测试失败 | 构建机时区不同 | 测试里显式指定时区 |
OutOfMemoryError 构建时 | Maven 堆太小 | export MAVEN_OPTS="-Xmx2g" |