Maven
Maven 翻译为"专家"、“内行”,是 Apache 下的一个纯 Java 开发的开源项目。基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。
Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。
Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。Maven 曾是 Jakarta 项目的子项目,现为由 Apache 软件基金会主持的独立 Apache 项目。
Maven 是一个项目管理工具,它包含着:
- 一个项目对象模型 (POM:Project Object Model)
- 一组标准集合
- 一个项目生命周期(Project Lifecycle)
- 一个依赖管理系统(Dependency Management System)
- 用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑
Maven优势举例
举个例子:
Maven的两个重要作用
- 依赖管理
- 项目的一键构建
依赖管理
Maven 的一个核心特性就是依赖管理。当我们涉及到多模块的项目(包含成百个模块或者子项目),管理依赖就变成一项困难的任务。Maven 展示出了它对处理这种情形的高度控制。
依赖管理:Maven过程对jar包的管理过程
maven 工程中不直接将 jar 包导入到工程中,而是通过在 pom.xml 文件中添加所需 jar包的坐标,这样就很好的避免了 jar 直接引入进来,在需要用到 jar 包的时候,只要查找 pom.xml 文件,再通过 pom.xml 文件中的坐标,到一个专门用于”存放 jar 包的仓库”(maven 仓库)中根据坐标从而找到这些 jar 包,再把这些 jar 包拿去运行。
项目的一键构建
我们的项目,往往都要经历编译、测试、运行、打包、安装 ,部署等一系列过程。
构建:项目从编译、测试、运行、打包、安装 ,部署整个过程都交给 maven 进行管理,这个
过程称为构建一键构建:整个构建过程,使用 maven 一个命令可以轻松完成整个工作
Maven安装
Maven的安装、环境变量配置以及配置文件的相关修改网上有很多教程
Maven仓库
maven 的工作需要从仓库下载一些 jar 包。比如说:本地的项目 A、项目 B 等都会通过 maven软件从远程仓库(可以理解为互联网上的仓库)下载 jar 包并存在本地仓库,本地仓库就是本地文件夹,当第二次需要此jar包时则不再从远程仓库下载,因为本地仓库已经存在了,可以将本地仓库理解为缓存,有了本地仓库就不用每次从远程仓库下载了。
Maven的三种仓库:
- 本地仓库 :用来存储从远程仓库或中央仓库下载的插件和 jar 包,项目使用一些插件或 jar 包,
优先从本地仓库查找(默认本地仓库位置在${user.dir}/.m2/repository
,${user.dir}
表示 windows 用户目录) - 私服仓库(远程仓库):一般来说是公司内部搭建的 Maven 私服,处于局域网中,访问速度较快,这个仓库中存放的 jar 一般就是公司内部自己开发的 jar。如果本地需要插件或者 jar 包,本地仓库没有,默认去远程仓库下载()
- 中央仓库 :在 maven 软件中内置一个远程仓库地址 http://repo1.maven.org/maven2 ,它是中央仓库,服务于整个互联网,它是由 Maven 团队自己维护,里面存储了非常全的 jar 包,它包含了世界上大部分流行的开源项目构件。(一般我们要再setting.xml文件中将地址修改为阿里云镜像地址)
jar包查找仓库顺序:
全局 setting 与用户 setting:maven 仓库地址、私服等配置信息需要在 setting.xml 文件中配置,分为全局配置和用户配置。
在 maven 安装目录下的有 conf/setting.xml 文件,此 setting.xml 文件用于 maven 的所有 project项目,它作为 maven 的全局配置。
如需要个性配置则需要在用户配置中设置,用户配置的 setting.xml 文件默认的位置在:
${user.dir} /.m2/settings.xml
目录中,${user.dir}
指 windows 中的用户目录。maven 会先找用户配置,如果找到则以用户配置文件为准,否则使用全局配置文件。
构建配置文件大体上有三种类型:
类型 | 在哪定义 |
---|---|
项目级(Per Project) | 定义在项目的POM文件pom.xml中 |
用户级 (Per User) | 定义在Maven的设置xml文件中 (%USER_HOME%/.m2/settings.xml) |
全局(Global) | 定义在 Maven 全局的设置 xml 文件中 (%M2_HOME%/conf/settings.xml) |
Maven工程目录结构
Maven工程标准目录结构如下:
作为一个 maven 工程,它的 src 目录和 pom.xml 是必备的
进入 src目录下的目录结构如下:
- src/main/java:存放项目的.java 文件
- src/main/resources:存放项目资源文件,如 spring, hibernate 配置文件
- src/test/java:存放所有单元测试.java 文件,如 JUnit 测试类
- src/test/resources:测试资源文件
- src/main/webapp:页面资源,js,css,图片等等
- target:项目输出位置,编译后的 class 文件会输出到此目录
- pom.xml:maven 项目核心配置文件
注意:如果是普通的 java 项目,那么就没有 webapp 目录
Maven常用命令
Maven 中有一些常见的命令(如果使用 IDEA 的话,可以不用命令,直接点点点就可以了):
常用命令 | 中文含义 | 说明 |
---|---|---|
mvn clean | 清理 | 这个命令可以用来清理已经编译好的文件 |
mvn compile | 编译 | 将 Java 代码编译成 Class 文件 |
mvn test | 测试 | 项目测试 |
mvn package | 打包 | 根据用户的配置,将项目打成 jar 包或者 war 包 |
mvn install | 安装 | 手动向本地仓库安装一个 jar |
mvn deploy | 上传 | 将 jar包(或war包)上传到私服 |
这里需要注意的是,这些命令都不是独立运行的,它有一个顺序。举个简单例子:
我想将 jar 上传到私服,那么就要构建 jar,就需要执行 package 命令,要打包,当然也需要测试,那就要走 mvn test 命令,要测试就要先编译…因此,最终所有的命令都会执行一遍。不过,开发者也可以手动配置不执行某一个命令,这就是跳过。一般来说,除了测试,其他步骤都不建议跳过。
Maven生命周期
Maven 对项目构建过程分为三套相互独立的生命周期,请注意这里说的是“三套”,而且“相互独立”,这三套生命周期分别是:
- Clean Lifecycle(清理生命周期):在进行真正的构建之前进行一些清理工作
- Default Lifecycle(默认生命周期):构建的核心部分,编译,测试,打包,部署等等
- Site Lifecycle(站点生命周期):生成项目报告,站点,发布站点
Maven概念模型
上面已经说到了:
Maven 是一个项目管理工具,它包含着:
-
项目对象模型 (POM:Project Object Model):每个 maven 工程都有一个 pom.xml 文件,通过 pom.xml 文件定义项目的坐标、项目依赖、项目信息、插件目标等。
-
标准集合:maven 将整个项目管理过程定义一组标准。比如:通过 maven 构建工程有标准的目录结构,有标准的生命周期阶段、依赖管理有标准的坐标定义等。
-
项目生命周期(Project Lifecycle):使用 maven 完成项目的构建,项目构建包括:清理、编译、测试、部署等过程,maven 将这些过程规范为一个生命周期。maven 通过执行一些简单命令即可生命周期的各个过程,比如执行
mvn compile
执行编译、执行mvn clean
执行清理。 -
依赖管理系统(Dependency Management System):通过 maven 的依赖管理对项目所依赖的 jar 包进行统一管理。比如:项目依赖 junit4.9,通过在 pom.xml 中定义 junit4.9 的依赖即使用 junit4.9,在pom.xml中进行依赖定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- 依赖关系 -->
<dependencies>
<!-- 此项目运行使用 junit,所以此项目依赖 junit -->
<dependency>
<!-- junit 的项目名称 -->
<groupId>junit</groupId>
<!-- junit 的模块名称 -->
<artifactId>junit</artifactId>
<!-- junit 版本 -->
<version>4.9</version>
<!-- 依赖范围:单元测试时使用 junit -->
<scope>test</scope>
</dependency>
</dependencies> -
用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑:Maven 管理项目生命周期过程都是基于插件完成的
IDEA中使用 Maven
(以后创建JavaSE项目,建议不使用骨架进行创建)
- 使用骨架创建maven的java工程
- 不使用骨架创建maven的java工程
- 使用骨架创建maven的web工程
在 IDEA 中,创建 Maven Web 项目,有两种思路:
- 首先创建一个 JavaSE 项目,然后手动将 JavaSE 项目改造成一个 JavaWeb 项目
- 创建项目时选择项目骨架,骨架就选择 webapp
两种方式中,推荐使用第一种方式。
Maven中的JavaWeb项目
简单的Servlet请求转发案例。代码所在仓库地址:https://gitee.com/qingyu1011/springboot_study/tree/master/Maven/Maven03-2
Maven中的JavaSE过程:从MySQL中获取数据
通过JDBC从MySQL中查出数据。代码所在仓库地址:https://gitee.com/qingyu1011/springboot_study/tree/master/Maven/Maven04
结果如下:
Maven依赖管理
maven 依赖版本管理— dependencyManagement
Maven 项目,如果需要使用第三方的控件,都是通过依赖管理来完成的。这里用到的一个东西就是 pom.xml 文件,概念叫做项目对象模型(POM,Project Object Model),我们在 pom.xml 中定义了Maven 项目的形式,所以,pom.xml 相当于是 Maven 项目的一个地图。就类似于 web.xml 文件用来描述三大 web 组件一样。
Maven坐标
举个例子:
1 | <dependencies> |
-
dependencies:在 dependencies 标签中,添加项目需要的 jar 所对应的 maven 坐标
-
dependency:一个 dependency 标签表示一个坐标
-
groupId:团体、公司、组织机构等等的唯一标识。团体标识的约定是它以创建这个项目的组织名称的逆向域名(例如 org.javaboy)开头。一个 Maven 坐标必须要包含 groupId。一些典型的 groupId 如 apache 的 groupId 是
org.apache
-
artifactId:artifactId 相当于在一个组织中项目的唯一标识符
-
version:一个项目的版本。一个项目的话,可能会有多个版本。如果是正在开发的项目,我们可以给版本号加上一个 SNAPSHOT,表示这是一个快照版(新建项目的默认版本号就是快照版)
-
scope:表示依赖范围
scope作用域
- compile:默认就是compile,什么都不配置也就是意味着compile。compile表示被依赖项目需要参与当前项目的编译,当然后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去
- test:scope为test表示依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。比较典型的如junit
- runntime:runntime表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过编译而已,说实话在终端的项目(非开源,企业内部系统)中,和compile区别不是很大。比较常见的如JSR×××的实现,对应的API jar是compile的,具体实现是runtime的,compile只需要知道接口就足够了。Oracle jdbc驱动架包就是一个很好的例子,一般scope为runntime。另外runntime的依赖通常和optional搭配使用,optional为true。我可以用A实现,也可以用B实现
- provided:provided意味着打包的时候可以不用包进去,别的设施(Web Container)会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。例如:servlet-api
- system:从参与度来说,也provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用
我们添加了很多依赖,但是不同依赖的使用范围是不一样的。最典型的有两个,一个是数据库驱动,另一个是单元测试。
数据库驱动,在使用的过程中,我们自己写代码,写的是 JDBC 代码,只有在项目运行时,才需要执行 MySQL 驱动中的代码。所以,MySQL 驱动这个依赖在添加到项目中之后,可以设置它的 scope 为 runtime,编译的时候不生效。
单元测试,只在测试的时候生效,所以可以设置它的 scope 为 test,这样,当项目打包发布时,单元测试的依赖就不会跟着发布。
依赖冲突
依赖冲突产生的原因:
在图中,a.jar 依赖 b.jar,同时 a.jar 依赖 d.jar,这个时候,a 和 b、d 的关系是直接依赖的关系,a 和 c 的关系是间接依赖的关系。
冲突解决:
- 先定义先使用
- 路径最近原则(直接声明使用)
以 spring-context 为例,下图中 x 表示失效的依赖(优先级低的依赖,即路径近的依赖优先使用):
上面这两条是默认行为
我们也可以手动控制。手动控制主要是通过排除依赖来实现,如下:
1 | <dependency> |
这个表示从 spring-context 中排除 spring-core 依赖。
Maven私服
Maven 仓库管理也叫 Maven 私服或者代理仓库。使用 Maven 私服有两个目的:
- 私服是一个介于开发者和远程仓库之间的代理
- 私服可以用来部署公司自己的 jar
Nexus 介绍
Nexus是Maven仓库管理器,也可以叫Maven的私服。Nexus是一个强大的Maven仓库管理器,它极大地简化了自己内部仓库的维护和外部仓库的访问。利用Nexus你可以只在一个地方就能够完全控制访问和部署在你所维护仓库中的每个Artifact。Nexus是一套“开箱即用”的系统不需要数据库,它使用文件系统加Lucene来组织数据。
Nexus不是Maven的核心概念,它仅仅是一种衍生出来的特殊的Maven仓库。
按照上述教程及官网,下载并解压后的nexus目录如下:
- nexus-3.30.0-01:安装目录
- sonatype-work:工作目录
进完成环境变量的配置后,在cmd中进入bin目录(我这里是:D:\Maven\nexus-3.30.0-01-win64\nexus-3.30.0-01\bin
),然后输入命令:nexus /run
即可运行。运行成功后访问:http://localhost:8081/
:
(网站加载还是挺好看的)
- 默认账号:admin
- 默认密码:临时密码是在sonatype-work\nexus3下的admin.password中(后续修改密码后这个文件将自动删除)
登录后会提示重新设置密码:
Nexus中的仓库
访问的仓库类型:
- hosted 宿主仓库:主要用于部署无法从公共仓库获取的构件以及自己或第三方的项目构件
- proxy 代理仓库:代理公共的远程仓库
- group 仓库组:Nexus 通过仓库组统一管理多个仓库,这样我们在项目中直接请求仓库组即可请求到仓库组管理的多个仓库
名称 | 说明 |
---|---|
proxy | 表示这个仓库是一个远程仓库的代理,最典型的就是代理 Maven 中央仓库 |
hosted | 宿主仓库,公司自己开发的一些 jar 存放在宿主仓库中,以及一些在 Maven 中央仓库上没有的 jar |
group | 仓库组,包含代理仓库和宿主仓库 |
virtual | 虚拟仓库 |
简单的说,就是你可以上传私有的项目到hosted,以及配置proxy以获取第三方的依赖(比如可以配置中央仓库的地址)。前面两个都弄好了之后,在通过group聚合给客户提供统一的访问地址。
管理本地仓库:
Nexus预定义了2个本地仓库,分别是maven-releases, maven-snapshots,。这里分别讲一下这两个预置的仓库都是做什么用的:
- maven-releases:这里存放我们自己项目中发布的构建, 通常是Release版本的
- maven-snapshots:这个仓库非常的有用, 它的目的是让我们可以发布那些非release版本, 非稳定版本
Nexus增加仓库:
增加本地用户:
配置私服(settings.xml)
Maven 的 conf/settings.xml 文件配置:
1 | <!--设置的maven本地仓库--> |
上传jar包到Nexus
直接上传jar包
在cmd中输入以下命令:
1 | mvn deploy:deploy-file -DgroupId=xxx.xxx -DartifactId=xxx -Dversion=xxx -Dpackaging=jar -Dfile=D:\xxx.jar -Durl=http://xxx.xxx.xxx.xxx:8081/repository/maven-releases/ -DrepositoryId=nexus |
相关说明:
- -DgroupId 为上传的jar的groupId
- -DartifactId 为上传的jar的artifactId
- -Dversion 为上传的jar的需要被依赖的时候的版本号
- -Dpackaging为jar
- -Dfile为jar包路径
- -Durl 为要上传的路径,-DrepositoryId 为repository的唯一标示,跟第3步中赋权配置的server相同
注意:-Dfile中的路径最好就在D盘的根目录D:\xxx.jar,不要D:\xxx\xxx\xxx\xxx.jar,这样可能会报错
示例:
1 | mvn deploy:deploy-file -DgroupId=org.olap4j -DartifactId=olap4j -Dversion=0.9.7.309-JS-3 -Dpackaging=jar -Dfile=D:\olap4j-0.9.7.309-JS-3.jar -Durl=http://192.168.10.68:8081/repository/maven-releases/ -DrepositoryId=nexus |
直接将项目发布到仓库中
在pom.xml中添加一下代码,和dependencies属于同一级别,在project级别下:
1 | <distributionManagement> |
添加完后,cd到pom.xml文件目录中运行:mvn deploy即可(或者在IDEA中直接点击deploy)
注意:id为要上传的repository的唯一标示,url为要上传的repository的路径
聚合工程
maven 依赖版本管理— dependencyManagement
所谓的聚合工程,实际上也就是多模块项目。在一个比较大的互联网项目中,项目需要拆分成多个模块进行开发,比如订单模块、VIP 模块、支付模块、内容管理模块、CMS、CRM 等等。这种拆分方式,实际上更接近于微服务的思想。在一个模块中,还可以继续进行拆分,例如分成 dao、service、controller 等。
多模块项目展示
1 | |--Maven04-parent |
以 javaboy-manger 为例,javaboy-manager 本身并不提供功能,它只负责管理他自己的子模块,而他的子模块每一个都无法独立运行,需要四个结合在一起,才可以运行。项目打包时,model、dao、service 都将打包成 jar,然后会自动将打包好的 jar 复制到 web 中,再自动将 web 打包成 war 包。
IDEA 中创建聚合工程
首先创建一个空的 Maven 项目。项目创建完成后,由于 parent 并不参与业务的实现,只是用来管理它的子模块,因此,可以删除src 目录。
然后在父工程上再new一个Module,可以发现IDEA已经给我们指定了父工程:
且当Maven04_manager创建完成后,观察Maven04_parent的pom.xml文件,可发现pom.xml文件中:
- 自动加上了packing属性,属性值为pom,表示这是一个聚合工程
- 多了modules节点,指明了它自己的子模块
Maven04_parent的pom.xml文件如下:
1 |
|
同时,注意Maven04_manager的pom.xml文件:
- 多了一个parent节点,这个parent节点描述了它的父模块的属性值
- 这个parent节点不仅仅是一个简单的父子关系描述,它还存在继承关系,一般我们可以在parent中统一定义依赖或者插件的版本号
Maven04_manager的pom.xml文件如下:
1 |
|
同时,由于Maven04_manager本身也是一个父工程,所以可以删除src目录并创建其子模块:
- Maven04manager-dao
- Maven04-manager-service
- Maven04-manager-model
- Maven04-manager-web
同时要手动设置Maven04manager-dao,Maven04-manager-service,Maven04-manager-model的packing属性值为jar
而由于 javaboy-manager-web不同于其他模块,web模块需要打包成 war。packing属性值为war,同时需要进行相关操作将其改造成web工程
参考文章
关于Maven的多模块之间的关系,或者本笔记没有提到过的一些知识点,可参照文章:
POM 标签大全详解
菜鸟教程中有POM 标签大全详解,可供参考