参考网站
Maven 基础
maven介绍
在了解Maven之前,我们先来看看一个Java项目需要的东西。首先,我们需要确定引入哪些依赖包。例如,如果我们需要用到commons logging,我们就必须把commons logging的jar包放入classpath。如果我们还需要log4j,就需要把log4j相关的jar包都放到classpath中。这些就是依赖包的管理。
其次,我们要确定项目的目录结构。例如,src
目录存放Java源码,resources
目录存放配置文件,bin
目录存放编译生成的.class
文件。
此外,我们还需要配置环境,例如JDK的版本,编译打包的流程,当前代码的版本号。
最后,除了使用Eclipse这样的IDE进行编译外,我们还必须能通过命令行工具进行编译,才能够让项目在一个独立的服务器上编译、测试、部署。
这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的Java项目管理和构建工具。
Maven就是是专门为Java项目打造的管理和构建工具,它的主要功能有:
- 提供了一套标准化的项目结构;
- 提供了一套标准化的构建流程(编译,测试,打包,发布……);
- 提供了一套依赖管理机制。
Maven项目结构
一个使用Maven管理的普通的Java项目,它的目录结构默认如下:
a-maven-project |
项目的根目录a-maven-project
是项目名,它有一个项目描述文件pom.xml
,存放Java源码的目录是src/main/java
,存放资源文件的目录是src/main/resources
,存放测试源码的目录是src/test/java
,存放测试资源的目录是src/test/resources
,最后,所有编译、打包生成的文件都放在target
目录里。这些就是一个Maven项目的标准目录结构。
所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven就可以正常使用。
我们再来看最关键的一个项目描述文件pom.xml
,它的内容长得像下面:
<project ...> |
其中,groupId
类似于Java的包名,通常是公司或组织名称,artifactId
类似于Java的类名,通常是项目名称,再加上version
,一个Maven工程就是由groupId
,artifactId
和version
作为唯一标识。
我们在引用其他第三方库的时候,也是通过这3个变量确定。例如,依赖org.slfj4:slf4j-simple:2.0.16
:
<dependency> |
使用<dependency>
声明一个依赖后,Maven就会自动下载这个依赖包并把它放到classpath中。
另外,注意到<properties>
定义了一些属性,常用的属性有:
project.build.sourceEncoding
:表示项目源码的字符编码,通常应设定为UTF-8
;maven.compiler.release
:表示使用的JDK版本,例如21
;maven.compiler.source
:表示Java编译器读取的源码版本;maven.compiler.target
:表示Java编译器编译的Class版本。
从Java 9开始,推荐使用maven.compiler.release
属性,保证编译时输入的源码和编译输出版本一致。如果源码和输出版本不同,则应该分别设置maven.compiler.source
和maven.compiler.target
。
通过<properties>
定义的属性,就可以固定JDK版本,防止同一个项目的不同的开发者各自使用不同版本的JDK。
小结
Maven是一个Java项目的管理和构建工具:
- Maven使用
pom.xml
定义项目内容,并使用预设的目录结构; - 在Maven中声明一个依赖项可以自动下载并导入classpath;
- Maven使用
groupId
,artifactId
和version
唯一定位一个依赖。
依赖管理
如果我们的项目依赖第三方的jar包,例如commons logging,那么问题来了:commons logging发布的jar包在哪下载?
如果我们还希望依赖log4j,那么使用log4j需要哪些jar包?
类似的依赖还包括:JUnit,JavaMail,MySQL驱动等等,一个可行的方法是通过搜索引擎搜索到项目的官网,然后手动下载zip包,解压,放入classpath。但是,这个过程非常繁琐。
Maven解决了依赖管理问题。例如,我们的项目依赖abc
这个jar包,而abc
又依赖xyz
这个jar包:
┌──────────────┐ |
当我们声明了abc
的依赖时,Maven自动把abc
和xyz
都加入了我们的项目依赖,不需要我们自己去研究abc
是否需要依赖xyz
。
因此,Maven的第一个作用就是解决依赖管理。我们声明了自己的项目需要abc
,Maven会自动导入abc
的jar包,再判断出abc
需要xyz
,又会自动导入xyz
的jar包,这样,最终我们的项目会依赖abc
和xyz
两个jar包。
我们来看一个复杂依赖示例:
<dependency> |
当我们声明一个spring-boot-starter-web
依赖时,Maven会自动解析并判断最终需要大概二三十个其他依赖:
spring-boot-starter-web |
如果我们自己去手动管理这些依赖是非常费时费力的,而且出错的概率很大。
依赖关系
Maven定义了几种依赖关系,分别是compile
、test
、runtime
和provided
:
scope | 说明 | 示例 |
---|---|---|
compile | 编译时需要用到该jar包(默认) | commons-logging |
test | 编译Test时需要用到该jar包 | junit |
runtime | 编译时不需要,但运行时需要用到 | mysql |
provided | 编译时需要用到,但运行时由JDK或某个服务器提供 | servlet-api |
其中,默认的compile
是最常用的,Maven会把这种类型的依赖直接放入classpath。
test
依赖表示仅在测试时使用,正常运行时并不需要。最常用的test
依赖就是JUnit:
<dependency> |
runtime
依赖表示编译时不需要,但运行时需要。最典型的runtime
依赖是JDBC驱动,例如MySQL驱动:
<dependency> |
provided
依赖表示编译时需要,但运行时不需要。最典型的provided
依赖是Servlet API,编译的时候需要,但是运行时,Servlet服务器内置了相关的jar,所以运行期不需要:
<dependency> |
最后一个问题是,Maven如何知道从何处下载所需的依赖?也就是相关的jar包?答案是Maven维护了一个中央仓库(repo1.maven.org),所有第三方库将自身的jar以及相关信息上传至中央仓库,Maven就可以从中央仓库把所需依赖下载到本地。
Maven并不会每次都从中央仓库下载jar包。一个jar包一旦被下载过,就会被Maven自动缓存在本地目录(用户主目录的.m2
目录),所以,除了第一次编译时因为下载需要时间会比较慢,后续过程因为有本地缓存,并不会重复下载相同的jar包。
唯一ID
对于某个依赖,Maven只需要3个变量即可唯一确定某个jar包:
- groupId:属于组织的名称,类似Java的包名;
- artifactId:该jar包自身的名称,类似Java的类名;
- version:该jar包的版本。
通过上述3个变量,即可唯一确定某个jar包。Maven通过对jar包进行PGP签名确保任何一个jar包一经发布就无法修改。修改已发布jar包的唯一方法是发布一个新版本。
因此,某个jar包一旦被Maven下载过,即可永久地安全缓存在本地。
注:只有以-SNAPSHOT
结尾的版本号会被Maven视为开发版本,开发版本每次都会重复下载,这种SNAPSHOT版本只能用于内部私有的Maven repo,公开发布的版本不允许出现SNAPSHOT。
后续我们在表示Maven依赖时,使用简写形式groupId:artifactId:version,例如:org.slf4j:slf4j-api:2.0.4。
Maven镜像
除了可以从Maven的中央仓库下载外,还可以从Maven的镜像仓库下载。如果访问Maven的中央仓库非常慢,我们可以选择一个速度较快的Maven的镜像仓库。Maven镜像仓库定期从中央仓库同步:
slow ┌───────────────────┐ |
中国区用户可以使用阿里云提供的Maven镜像仓库。使用Maven镜像仓库需要一个配置,在用户主目录下进入.m2
目录,创建一个settings.xml
配置文件,内容如下:
<settings> |
配置镜像仓库后,Maven的下载速度就会非常快。
搜索第三方组件
最后一个问题:如果我们要引用一个第三方组件,比如okhttp
,如何确切地获得它的groupId
、artifactId
和version
?方法是通过search.maven.org搜索关键字,找到对应的组件后,直接复制
命令行编译
在命令中,进入到pom.xml
所在目录,输入以下命令:
$ mvn clean package |
如果一切顺利,即可在target
目录下获得编译后自动打包的jar。
在IDE中使用Maven
几乎所有的IDE都内置了对Maven的支持。在Eclipse中,可以直接创建或导入Maven项目。如果导入后的Maven项目有错误,可以尝试选择项目后点击右键,选择Maven - Update Project…更新
小结
Maven通过解析依赖关系确定项目所需的jar包,常用的4种scope
有:compile
(默认),test
,runtime
和provided
;
Maven从中央仓库下载所需的jar包并缓存在本地;
可以通过镜像仓库加速下载。
构建流程
Maven不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布,等等。
Lifecycle和Phase
使用Maven时,我们首先要了解什么是Maven的生命周期(lifecycle)。
Maven的生命周期由一系列阶段(phase)构成,以内置的生命周期default
为例,它包含以下phase:
- validate 校验
- initialize 初始化
- generate-sources 生成源码
- process-sources 处理源码
- generate-resources 生成资源
- process-resources 处理资源
- compile 编译
- process-classes 处理-classes
- generate-test-sources 生成测试源码
- process-test-sources 处理测试源码
- generate-test-resources 生成测试资源
- process-test-resources 处理测试资源
- test-compile 测试编译
- process-test-classes 处理测试-classes
- test 测试
- prepare-package 准备打包
- package 打包
- pre-integration-test 预综合测试
- integration-test 综合测试
- post-integration-test 综合测试之后
- verify 核实
- install 安装
- deploy 部署
如果我们运行mvn package
,Maven就会执行default
生命周期,它会从开始一直运行到package
这个phase为止:
- validate
- initialize
- …
- prepare-package
- package
如果我们运行mvn compile
,Maven也会执行default
生命周期,但这次它只会运行到compile
,即以下几个phase:
- validate
- initialize
- …
- process-resources
- compile
Maven另一个常用的生命周期是clean
,它会执行3个phase:
- pre-clean
- clean (注意这个clean不是lifecycle而是phase)
- post-clean
所以,我们使用mvn
这个命令时,后面的参数是phase,Maven自动根据生命周期运行到指定的phase。
更复杂的例子是指定多个phase,例如,运行mvn clean package
,Maven先执行clean
生命周期并运行到clean
这个phase,然后执行default
生命周期并运行到package
这个phase,实际执行的phase如下:
- pre-clean
- clean (注意这个clean是phase)
- validate (开始执行default生命周期的第一个phase)
- initialize
- …
- prepare-package
- package
在实际开发过程中,经常使用的命令有:
mvn clean
:清理所有生成的class和jar;
mvn clean compile
:先清理,再执行到compile
;
mvn clean test
:先清理,再执行到test
,因为执行test
前必须执行compile
,所以这里不必指定compile
;
mvn clean package
:先清理,再执行到package
。
大多数phase在执行过程中,因为我们通常没有在pom.xml
中配置相关的设置,所以这些phase什么事情都不做。
经常用到的phase其实只有几个:
- clean:清理
- compile:编译
- test:运行测试
- package:打包
Goal
执行一个phase又会触发一个或多个goal:
执行的Phase | 对应执行的Goal |
---|---|
compile | compiler:compile |
test | compiler:testCompile surefire:test |
goal的命名总是abc:xyz
这种形式。
其实我们类比一下就明白了:
- lifecycle相当于Java的package,它包含一个或多个phase;
- phase相当于Java的class,它包含一个或多个goal;
- goal相当于class的method,它其实才是真正干活的。
大多数情况,我们只要指定phase,就默认执行这些phase默认绑定的goal,只有少数情况,我们可以直接指定运行一个goal,例如,启动Tomcat服务器:
$ mvn tomcat:run |
小结
Maven通过lifecycle、phase和goal来提供标准的构建流程。
最常用的构建命令是指定phase,然后让Maven执行到指定的phase:
- mvn clean
- mvn clean compile
- mvn clean test
- mvn clean package
通常情况,我们总是执行phase默认绑定的goal,因此不必指定goal。