一、POM文件
pom.xml,全称Project Object Model,项目对象模型。可以把它理解成这个项目的身份证加说明书,身份证告诉别人我是谁,说明书告诉Maven该怎么构建我。
在pom.xml中,有三个基本元素构成了Maven的坐标:
groupId:公司域名反写加项目名,比如com.qcby。
artifactId:项目名或模块名,比如SchoolShop。
version:版本号,比如0.0.1-SNAPSHOT。
这三个元素合起来全球唯一,就像经纬度定位一个地点,Maven用坐标定位一个构件(JAR或WAR包)。本地仓库的目录结构正是按照groupId/artifactId/version来组织的,你打开.m2/repository文件夹就能看到这种层级关系。
版本号里的SNAPSHOT表示快照版,意思是还在开发中、没定稿的版本。对应的RELEASE则是正式发布版。SNAPSHOT可以反复覆盖上传,RELEASE一旦发布就不应该再改。我们一般使用这两个。
二、依赖管理
1. 依赖是什么
写Java代码时,我们要做单元测试要引入JUnit,操作数据库要引入MyBatis,写Web接口要引入Spring Boot。这些外部的JAR包就是依赖。
Maven的依赖管理解决了三个痛点:第一,不用手动下载JAR包,Maven自动从仓库拉取;第二,不用管依赖的依赖,比如你引入Spring Boot,它依赖的几十个包Maven会自动帮你下载,这叫依赖传递;第三,不用愁版本冲突,Maven有一套仲裁机制。
在pom.xml中声明依赖的写法如下:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
2. 依赖范围(scope)
Maven在编译、测试、运行三个场景下需要不同的classpath。scope标签用来控制某个依赖在哪个阶段生效。
compile(默认):编译、测试、运行都有效,几乎所有业务包都用这个。
test:只对测试有效,编译主代码和运行时无效,典型例子是JUnit。
provided:编译和测试有效,运行时无效,典型例子是Servlet API,因为Tomcat容器自己已经提供了。
runtime:编译时不需要,但测试和运行时需要,比如JDBC驱动,编码时只需接口,运行时才需要具体实现。
开发时写HttpServletRequest需要servlet-api.jar才能编译通过,但部署到Tomcat时Tomcat自己就有这个包,如果打包时把servlet-api也打进去,反而会和Tomcat的版本冲突。所以provided就是告诉Maven:编译和测试时我给你,打包和运行时你就别带了,容器会提供。
3. 依赖传递
如果A依赖B,B又依赖C,那么A是否自动依赖C?不一定,要看B对C的依赖范围。
规则是这样:如果B对C是compile范围,A会继承C,但范围可能会降级;如果B对C是test或provided范围,传递就断了,A不会依赖C;如果B对C是runtime范围,且A对B是compile,则C会以runtime范围传递给A。
实际开发中常遇到的痛点:你引入了一个第三方包,结果它带进来一堆你不需要的传递依赖,甚至引起版本冲突。这时可以用exclusions把特定的传递依赖排除掉。例如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
这样就把默认的日志框架排除掉了,你可以换用自己的日志实现。
4. 可选依赖和依赖阻断
如果你写了一个公共库,内部依赖了某个包,但你希望使用你这个公共库的项目不自动继承这个依赖,这时可以使用optional true。设置了optional后,别人依赖你的项目时,不会被动引入这个包,需要他们自己显式声明。
另外,如果你完全不想让某个传递依赖被带进来,也可以直接在dependency中配置exclusions。
三、仓库
Maven仓库就是存放构件(JAR、WAR、POM等)的地方。当你执行mvn compile时,Maven会从仓库中读取所需的依赖。
仓库分为三类:
本地仓库:默认在用户目录下的.m2/repository。第一次执行Maven命令时创建,所有下载的依赖都会先存到这里。
中央仓库:Maven官方维护的公共仓库,地址是repo1.maven.org,包含了绝大多数开源Java构件。
远程仓库:除了中央仓库之外的私服或镜像,比如阿里云镜像、公司私服。
当项目需要一个依赖时,Maven的查找顺序是:本地仓库 -> 私服(如果有配置)-> 中央仓库。本地没有就去远程下载,下载后缓存到本地,下次直接用。
可以通过修改settings.xml中的localRepository标签来改变本地仓库的位置,所有项目共享这个目录,便于管理。
四、生命周期
Maven最巧妙的设计之一就是生命周期。它抽象出一套构建流程,你只需要告诉它"我要打包",它就会自动执行从编译、测试到打包的所有前置步骤。
Maven有三套独立的生命周期:
1. Clean生命周期
阶段顺序:pre-clean -> clean -> post-clean。执行mvn clean会删除target目录,清理上一次构建的残留。
2. Default生命周期
这是最完整也最常用的一套,包含二十多个阶段,其中我们最常打交道的几个是:
compile:编译src/main/java下的源码,命令是mvn compile。
test:执行单元测试,命令是mvn test。
package:打包成JAR或WAR,命令是mvn package。
install:安装到本地仓库,命令是mvn install。
deploy:部署到远程私服,命令是mvn deploy。
最重要的铁律:执行任何一个阶段,它之前的所有阶段都会自动按顺序执行。比如你执行mvn package,Maven会自动先compile、再test、最后package。你不需要手动敲多个命令。
3. Site生命周期
阶段顺序:pre-site -> site -> post-site -> site-deploy。用于生成API文档和报告,一般用得不频繁。
生命周期本身是抽象的,实际工作由插件完成。比如compile阶段绑定的是maven-compiler-plugin,test阶段绑定的是maven-surefire-plugin。这种设计让你可以方便地替换或扩展插件行为。
五、继承与聚合
当项目拆分出多个模块(如common、dao、service、web)时,每个模块都有自己的pom.xml。如果没有统一管理,版本号、插件配置会散落在各处,维护起来极其痛苦。
1. 继承
创建一个父工程,它的packaging必须为pom。将公共配置集中到父工程中,子模块通过parent标签继承。
父工程可以做两件事:
第一,直接声明依赖,写在dependencies里,所有子模块自动继承这些依赖,适用于所有模块都必须用的包(如Lombok、日志)。
第二,通过dependencyManagement统一版本。子模块不会自动引入这些依赖,但如果子模块声明了相同的groupId和artifactId,则可以省略版本号,由父工程统一管控。这是最推荐的做法,既能统一版本,又不会强制给子模块添加不需要的依赖。
父工程示例:
<dependencyManagement>
<dependencies>
<dependency>groupId org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子模块引用时就不需要写版本号了。
2. 聚合
聚合解决的是每次都要逐个模块去构建的麻烦。在父工程的modules中列出所有子模块,然后对父工程执行mvn install,Maven会按照模块间的依赖关系依次构建所有子模块。
继承和聚合通常一起使用,一个父工程既当版本管家,又当构建总管。
六、私服(Nexus)
1. 为什么需要私服
假设团队有10个人,每个人都在本地从中央仓库下载依赖,同一个JAR被下载10次,浪费带宽和时间。更关键的是,自己团队开发的公共模块怎么共享?总不能每次都把JAR包发邮件吧。
私服的两个核心作用:对外,私服充当中央仓库的代理,团队成员统一从私服下载依赖,私服首次下载后会缓存,后续走内网,速度飞快;对内,自己开发的模块发布到私服,其他人像引用第三方包一样引用它。
2. Nexus的仓库类型
Nexus将仓库分为三类:
Hosted(宿主仓库):存放本公司自己开发的构件,分为releases(正式版)和snapshots(快照版)两个仓库。版本号带SNAPSHOT的上传到snapshots,不带的上传到releases。
Proxy(代理仓库):代理外部公共仓库(如中央仓库、阿里云),私服没有的构件通过它去下载并缓存。
Group(仓库组):将多个仓库合成一个统一的访问地址,客户端只需配置这一个地址。比如把releases、snapshots、central组成一个public组。
3. Nexus安装与启动
下载Nexus 2.x或3.x压缩包,解压到无中文路径的目录。修改conf/nexus.properties可配置端口(默认8081)、数据存储目录等。以管理员身份运行cmd,进入bin目录,执行nexus.bat install注册为Windows服务,然后nexus.bat start启动。浏览器访问http://localhost:8081/nexus,默认账号admin,密码admin123登录。
4. 将项目发布到私服
第一步:在Maven的settings.xml中配置上传账号密码:
<servers>
<server>
<id>releases</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>
第二步:在项目的pom.xml中配置分发地址:
<distributionManagement>
<repository>
<id>releases</id>
<url>http://localhost:8081/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<url>http://localhost:8081/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
注意:id必须与settings.xml中server的id一致。
第三步:执行mvn deploy。Maven会根据版本号是否带SNAPSHOT自动决定上传到哪个仓库。
5. 从私服下载依赖
在settings.xml中配置镜像,将所有请求拦截并指向私服:
<mirror>
<id>nexusmaven</id>
<mirrorOf>*</mirrorOf>
<url>http://localhost:8081/nexus/content/groups/public/</url>
</mirror>
整个团队的依赖下载都走私服,提高工作效率。
6. 第三方JAR发布到私服
当遇到官方仓库没有的第三方JAR,可以用以下命令直接上传:
mvn deploy:deploy-file -DgroupId=com.alibaba -DartifactId=fastjson -Dversion=1.1.37 -Dpackaging=jar -Dfile=fastjson-1.1.37.jar -Durl=http://localhost:8081/nexus/content/repositories/thirdparty/ -DrepositoryId=thirdparty
在settings.xml中配置thirdparty对应的server账号密码。
感谢浏览我的博客,希望这篇博客对你有所帮助。