maven 是一种软件项目管理和理解的工具。基于项目对象模型的概念。
前言
maven 一直当作导jar包的工具,在实际开发中遇到了maven基础知识不够的大坑
本篇大部分复制于茶轴的青春的文章《一文带你彻底搞懂 Maven》
简介
maven是一种软件项目管理和理解的工具。基于项目对象模型的概念。
定位
多数博客或者视频都将maven定义为自动化构建工具。那什么是自动化构建工具呢?
首先来解释构建
- 一个BS项目最终运行的并不是动态web工程本身,而是这个动态web工程“编译的结果”
- 将java源文件变成字节码,交给JVM去执行
- 编译
- 部署
构建各个过程的步骤
清理: 将以前编译得到的旧字节码删除掉
编译: 将java源代码变成字节码
测试: 执行test文件夹中的测试程序
报告: 显示测试程序执行的结果
打包: 动态Web工程打成war包,Java工程打成jar包
安装: Maven的特定概念—将打包得到的文件复制到”仓库”中指定的位置
部署: 将动态Web工程生成的war包复制到Servlet容器中指定的目录下,使其可以运行
自动化构建,其实上述步骤,在 elipse 和 IDEA 中也可以完成,只不过没那么标准。
既然 IDE 已经可以完成这些工作了,那么还要 maven 干什么呢?
日常开发中,以下几个步骤是我们经常走的
编译、打包、部署、测试
这几个步骤是程式化的,没有太大的变数或者说根本就没有变数。程序员们很希望从这些重复的工作中脱身出来,将这些重复的工作交给工具去做。此时Maven的意义就体现出来了,它可以自动的从构建过程中的起点一直执行到终点。
核心概念
POM
POM: a project object model. 项目对象模型
学习 Maven 就是学习 pom.xml 文件中的配置
坐标
使用如下三个向量在 Maven 的仓库中唯一的确定一个 Maven 工程
1 | [1] groupid:公司或组织的域名倒序+当前项目名称 |
引入对象的依赖非常的简单,去Maven的中央仓库,输入jar包的名字,选择需要的版本,然后将复制仓库给出的依赖,粘贴在pom.xml文件中的
仓库
仓库可以理解为存放jar包的地方。
本地仓库: 当前电脑上的部署的仓库目录,为当前电脑上的所有Maven工程服务。默认在用户家目录下的 ~/.m2/repository
远程仓库: 私服,搭建在局域网环境中,为局域网范围内的所有Maven工程服务。
中央仓库: 架设在Internet上,为全世界范围内所有的Maven工程服务。
中央仓库镜像: 为了分担中央仓库的流量,提升用户的访问速度。如阿里云
生命周期
Maven 生命周期定义了各个构建环节的执行顺序,有了这个清单,Maven 就可以自动化的执行构建命令了。
Maven有三套互相独立的生命周期
- Clean Lifecycle 在进行真正的构建之前进行一些清理工作
- Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等
- Site Lifecycle 生成项目报告,站点,发布站点
它们是相互独立的,你可以仅仅调用 clean 来清理工作目录,仅仅调用 site 来生成站点。
当然你也可以直接运行 mvn clean install site 运行所有这三套生命周期。
这个生成站点的意思就是: 在本地生成有关你项目的相关信息。具体没用过。。感兴趣的自看使用maven 插件site 生成站点
Clean生命周期
1 | 1 pre-clean 执行一些需要在 clean 之前完成的工作 |
Site生命周期
1 | 1 pre-site 执行一些需要在生成站点文档之前完成的工作 |
Default 生命周期是 Maven 生命周期中最重要的一个,绝大部分工作都发生在这个生命周期中。这里,只解释一些比较重要和常用的阶段
1 | validate |
maven 生命周期(lifecycle)由各个阶段组成,每个阶段由 maven 的插件 plugin 来执行完成。当我们执行的 maven 命令需要用到某些插件时,Maven 的核心程序会首先到本地仓库中查找。
依赖的范围
经常可以看到 pom 配置 dependency 时出现 scope。
1 | <dependency> |
提一下<scope>
的含义
compile: 默认值 他表示被依赖项目需要参与当前项目的编译,还有后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去
test: 依赖项目仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,例如:junit
runtime: 表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过了编译而已。例如JDBC驱动,适用运行和测试阶段
provided: 打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是打包阶段做了exclude操作
system: 从参与度来说,和provided相同,不过被依赖项不会从maven仓库下载,而是从本地文件系统拿。需要添加systemPath的属性来定义路径
踩坑现场 IDEA 在使用Maven项目时,未加载 provided 范围的依赖包,导致启动时报错
明明导入了依赖也确认过没有jar冲突,死活说找不到java.lang.ClassNotFoundException,最后才发现是Run Application时,IDEA未加载 provided 范围的依赖包,导致启动时报错(eclipse里面好像默认会加载,所以在那边是能正确运行的)
主要关注 compile、test、provided
1 | ① complie |
依赖的传递性
模块A依赖于模块B。模块B又依赖于C模块,则模块A内会有C模块。
但是有时候会存在jar冲突,导致我不想用C模块的依赖
可以这样排除依赖
解决 jar 包冲突, 可以参考下面文章,个人用的最多的是 Maven Healper
依赖的原则
[1] 路径最短者优先
[2] 路径相同时,先声明者优先
这里指的先声明者优先,是指 A、B 模块在A模块中 denpendency 标签配置的先后顺序。谁在上面,谁就是先声明者。
继承
继承可以帮我们做什么?统一管理依赖的版本。一个典型的例子就是:现在项目下A、B、C三个模块,三个模块都依赖着不同的 Junit 的版本,依赖范围为 test 时,依赖又不能传递过来,但是我们希望能够统一Junit版本,可能有的人自然会想到,你让A、B、C模块pom中的Junit依赖版本写一致不就行了,这也是个办法。但是 maven 在设计的时候估计也考虑到了这种情况,这称之为继承。
继承该如何使用?
首先需要创建一个 maven 父工程,注意指定打包方式为 pom
1 | <groupId>com.cxkStudy.maven</groupId> |
再创建一个maven工程,在该模块中指定它的父工程是谁
1 | <parent> |
在父项目中确定子模块的位置
1 | <modules> |
module 标签中填的是相对于父项目的位置。同时这也就是聚合
这样就可以在父工程里面指定Junit的版本, 在子模块使用Junit的时候不写版本就好了
项目顶层(即父工程)的POM文件中,会看到dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。
父工程中执行Junit的版本
1 | <dependencyManagement> |
子模块中引入Junit的时候不指定版本
1 | <dependency> |
请注意配置继承后, 执行安装命令要先安装父工程。
聚合
为什么要使用聚合?
将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进
行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。
聚合的好处还有继承,可以在子模块中不需要写相同依赖的版本了,上面有说。
关于继承、聚合的理解可以结合一个实际的项目来参考,我这边参考的是百度的图数据库hugegraph
参考链接