其实很多人,对Maven理解的并不深
作者:代堂鸣
Maven是每一位Java工程师每天都会接触的工具,但据我了解,其实很多应用开发人员对Maven理解的并不深。他们会告诉你,Maven就是一个依赖管理工具,平台组都会配置好,不用管。得到的回答,仅仅是一个小小的局部,管中窥豹那般的视角。
对想在这个领域里有所建树的人,先得热爱行业,才能沉浸在其中,面对艰难困苦的挑战也乐此不疲、不觉得苦;其次要有目标,管理好自己的一亩三分地之后,要想办法看到整个系统是怎么运转的。
当然也可以每天得过且过,或是每天费尽心力的去研究业务、看懂那些“变态”的代码。可能刚开始两者看起来没有区别,但每过5年左右的时间,量变都产生质变,就会发现两种人之间已经形成了巨大的差距。前者的路越走越宽阔,后者还是一成不变,也许渐渐地开始为工作发愁。
现在很多面试在问面试者项目经验的时候,经常会先让面试者先介绍项目背景、整体技术和业务结构,最后才会进入到自己真正负责的内容。所以只关注自己这一块是不行的,我们需要转变态度、提升眼界。有点说远了。
本文分五个部分来谈:
一、什么是Maven、有什么用
二、Maven安装、配置与运行
三、Maven的坐标与依赖说明
四、Maven仓库与私服的介绍
五、Maven的生命周期与插件
一、什么是Maven、有什么用
Maven这个单词来自于犹太语,意为知识的积累,最初在Jakata Turbine项目中用来简化构建过程。根据Maven的创立者Jason Van Zyl的介绍,最早的原型代码是在2001年提交的,2003年它成为了Apache的顶级项目。2010年10月Maven3正式发布,这时几乎成了Java构建的事实标准。
Maven被中国程序员接受应该是始于2008年,随后得到了越来越多的拥护者,现在也仍是一个被广泛使用的项目构建工具,通过将项目开发和管理过程抽象成一个项目对象模型(POM),然后将配置信息定义在pom.xml文件中。
那怎么通过pom.xml的配置就能获取到jar包、pom.xml配置文件从何而来等等,后面会介绍。
1.1 什么是Maven
官方解释是这么说的,“Maven是apache软件基金会组织维护的一款自动化构建工具,专注服务于Java平台的项目构建和依赖管理”。
通俗的说,Maven就是一个由Java语言编写的开源项目管理管理工具,主要用于管理Java依赖,还可以用于编译、打包以及发布。
特点列举如下:
跨平台:无论是在Windows上,还是在Linux或Mac上,都可以使用同样的命令,接触过一些项目的朋友会有更深的体会。
易扩展:Maven命令都是以插件的形式集成进来的,可以将自己开发一些maven插件给其他人使用,非常方便开发发布项目。
1.2 Maven有什么用
Maven的基本原理很简单,采用远程仓库和本地仓库以及一个核心的配置文件pom.xml,pom.xml中定义的jar文件从远程仓库下载到本地仓库,各个项目使用同一个本地仓库的jar,同一个版本的jar只需下载一次,而且避免每个应用都去拷贝jar。
也不用手动去添加jar包(最早的时候都是手动导入jar,使用Ant之类的编译java项目),同时它采用了插件体系架构,所以maven的核心非常小,只有几兆大小的文件,在执行maven任务时,才会自动下载需要的插件。
那Maven解决了啥痛点,有啥好处呢?
(1)统一集中管理好所有的依赖包
传统项目中要升级包的时候,必须将原来的包找出来删掉,然后到官网下载,再把新的包放进去。过程中有可能出现下载的版本并不是想要的,又得重新找。
不仅浪费了查找下载和导入包的时间成本,还极大的增加了学习成本。
借助Maven只需要改一下pom文件中相应的版本即可,不用自己下载jar包 ,pom文件会自动下载并管理jar包,省去了手工劳动。在需要的时候,可直接通过简单的描述文件告诉 Maven,它会自动帮助程序员找出来,集成到项目中。
比如,用到了a-1.0.jar,而a-1.0.jar依赖于b-1.1.jar和c-1.5.jar,当我们通过Maven把a-1.0.jar引入之后,b-1.1.jar和c-1.5.jar会自动被引入进来。
(2)能够自动解决重复和冲突问题
同样的jar包重复的出现在不同的项目工程中,需要不停的复制粘贴。通过Maven能够自动解决重复,并为绝大部分的构建任务提供了已实现的插件,不再需要定义过程,甚至不需要再去实现这些过程中的一些任务。
再如,使用Maven关联jar就可以配置引用jar的版本,避免冲突,同时让开发人员的工作更轻松。
(3)可以统一每个项目的构建过程
对于非常庞大的项目,如银行核心系统可以分为存款、贷款、公共等,通常不使用package来划分模块,而是每一个模块对应一个工程,利于分工协作,而且模块之间还是可以发送消息的。
借助于Maven可以通过将一个项目拆分成多个工程,实现不同项目的兼容性管理。
除了这些,Maven还有以下作用:
集成编译,测试,打包,发布,部署,等一条龙服务。
开发项目不依赖开发工具,可随意挑选支持Maven的IDE。
采用同一种项目结构,比如:Java文件、资源文件、测试用例、静态资源、编译之后的class、打包之后jar的位置等等各种文件的位置。
总体来说,Maven算是所有开发者的福音。
二、Maven安装、配置与运行
我们要在自己的PC上架设大型机的z/OS(MVS)系统、写COBOL程序,需要安装仿真软件HercGUI和PCOMM,配置CONF文件等。写Java代码也类似,需要先安装jdk,要使用Maven,那么就需要在PC上安装Maven,然后了解Maven命令的执行过程,再进行相关配置。
问:系统运行java或者其他外部命令的时候,系统是如何找到这些命令的?
答:window中有个系统变量PATH,这个PATH的值是由很多目录的地址组成的,当我们执行一个命令的之后,系统会去PATH对应的所有目录中寻找我们运行的命令,找到了就可以直接运行,比如想快速启动其他的一些软件,可以将这些软件的设置到PATH变量中,就能在cmd命令中快速启动了。
2.1 maven安装
Eclipse自带的Maven通常会比较新,但不一定很稳定,而且往往也和在命令行使用的Maven不是同一个版本。比如常见的jar包下载不全或者是install打包报错等等。
主要是因为较新版本的Maven存在很多不稳定因素,容易造成一些难以理解的问题;其次,除了IDE,也经常还会使用命令行的Maven,如果版本不一致,容易造成构建行为的不一致,这是我们所不希望看到的。
所以应该在IDE中配置Maven插件时使用与命令行一致的Maven,最好就是不要用自带的maven,改为我们自己安装的Maven。
步骤如下:
检查,当前系统JAVA_HOME的环境变量
安装,下载并解压至非中文无空格路径下
配置,环境变量(MAVEN_HOME,Path)
验证,mvn -v 命令查maven版本信息
2.2 maven目录结构
bin:存放可以执行的文件
boot:含plexus-classworlds类加载器框架(不必关心)
conf:存放maven的配置文件(settings.xml)
lib:第三方的jar包,这些jar包位于lib中
settings.xml中有个localRepository标签,主要是配置本地仓库的路径,即本地下载的jar存放路径。当maven从远程仓库下载下来的插件,以及以后所有所用到的jar包都会放在这个目录中。
推荐采取用户范围的settings.xml,即系统用户目录.m2下的settings.xml,主要是为了避免无意识地影响到系统中的其他用户,其次为了方便升级,升级时不需要触动settings.xml文件。
在Eclipse环境中,点击菜单栏中的Window,然后选择Preferences,在弹出的对话框中展开左边的Maven项,选择User Settings子项,在右边的面板中,单击Browse...按钮,然后选择对应的settings.xml文件,设置完毕之后点击OK按钮。
2.3 mvn命令的执行过程
运行mvn命令a后,系统会到环境变量PATH对应的所有目录中寻找mvn命令,会在bin中找到可执行的mvn文件
执行mvn命令,并查找mvnsetting.xml文件,若在C:\Users\用户\.m2中没找到则到conf中查到并执行maven程序
maven会到本地仓库的路径下,查找是否存在a插件,若没有则到中央仓库[repo.maven.apache.org]下载插件
运行插件,若系统找不到插件则会报错。
2.4 mvn常用命令说明
mvn clean:清理(默认为target文件夹)
mvn clean compile:清理并编译源代码至target文件夹
mvn clean test:运行清理和测试编译的源代码
mvn clean package:运行清理和打包
mvn clean install:运行清理并将打好的包安装到本地仓库
mvn clean deploy:运行清理和发布(部署)
上面的命令大部分都是连写的,也可以拆分分别执行,主要看个人喜好和使用需求,Eclipse Run as对maven项目会提供常用的命令。
三、Maven的坐标与依赖说明
3.1 Maven的坐标
在数学中,以平面直角坐标系为例,x轴通常画成水平方向,y轴通常画出竖直方向。坐标(x,y)表示这个点距离x轴为y,距离y轴为x的一点,任何一个坐标都能唯一标识该平面中的一点。
在生活中,地址位置也可以看成一种坐标。比如网购时需要填写地址信息,包括省、市、区、村/街道、小区名、楼号、单元号、门牌号等,那么快递小哥就会根据填写的具体地址派件。
(1)什么是坐标(Coordinates)?
在Maven中,坐标就好比每一个Java构件的身份证,通过坐标元素在仓库中进行唯一标识。 坐标元素包括groupId、artifactId、version、packaging、classfier。其中groupId、artifactId、version是必须要定义的,packaging是可选的(默认为jar),而classfier是不能直接定义的,需要结合插件使用。
坐标元素如下:
groupId:组id,机构名,公司名
artifactId:构件id,产品名,模块名
artifactId:版本号,例如1.0.0
package:构件的打包方式,如jar、war
(2)为什么使用坐标?
Maven中的Java构件非常多,如果Maven中没有坐标的概念,那么就无法来区分这些构件。不然使用了spring,去spring的官网下载jar包;使用hibernate,去hibernate的官网下载Jar包;使用Log4j,去log4j的官网下载jar包…..
有了Maven之后,只要我们提供正确的坐标元素,Maven就能找到对应的构件,先查找本地仓库,找不到再到远程仓库下载,如果没有配置远程仓库,会默认从中央仓库地址下载构件。
所以在项目中,对maven创建每一个项目、项目中的构建标注其坐标信息,算是强制性要求。只有这样,其他项目才能引用该项目的构件。
理解了Maven坐标,接下来就可以开始学习Maven的依赖管理,学习之前我们先看看Maven项目的核心 pom文件。
(3)pom文件介绍
POM(Project Object Model,项目对象模型)是Maven工程的核心配置文件,是一个XML文件,定义了项目的基本信息,用于描述项目如何构建,声明项目依赖,版本号,等等。
定义的POM稳定后,日常的Java代码开发工作中基本不会涉及到POM的修改,它能让项目对象模型最大程度地与实际代码相独立,可以称之为解耦。接着来,看看pom.xml文件主要标签的含义(坐标元素已介绍):
project:pom.xml文件中的顶层元素
modelVersion:指明POM使用的对象模型的版本(很少改)
name:项目的显示名称,通常用于maven产生的文档中
url:指定项目站点,通常用于maven产生的文档中
description:描述此项目,通常用于maven产生的文档中
properties:pom文件中的配置信息,可以配置全局变量
dependencies:依赖配置集,里面可以添加需要的jar的依赖信息
3.2 依赖说明
比如你要过河,没有桥怎么办,可以去借一条小船渡过去。那你与小船的关系仅仅是使用(借用)的关系。表现在代码上,可以理解为需要依赖一些东西才能实现相应的功能。
(1)什么是依赖?
在银行核心项目开发中也是同理,比如“存款”模块的交易需要查询客户信息,那么就依赖“客户”模块,再如“存款”模块的交易需要记账,那么就依赖“会计”模块。这就是所谓的依赖关系。
前文也提到了,这是maven的好处之一,仓库里面有各种各样的包,需要什么在pom.xml中通过坐标信息,添加依赖就好了,然后将这些信息放入pom.xml文件中的dependencies元素中即可。
(注:如果依赖的是自己或者团队开发的maven工程,需要先使用 install命令把被依赖的 maven工程的 jar包导入到本地仓库中)
根元素下project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。大部分依赖声明只用包含基本坐标(groupId、artifactId、version),但在一些特殊情况下,其他元素至关重要:
type:依赖的类型,对应于项目坐标定义的packaging(默认jar)
scope:依赖的范围,下文会详解
optional:标记依赖是否可选
exclusions:排除传递性依赖,下文会详解
(2)依赖范围
依赖范围是用于决定pom.xml加载进来的jar包,在什么范围内使用生效,范围包括编译,运行,测试等场景。
比如,junit的jar包只有在测试的时候会用到,在项目部署的时候,用不到这个包;再如tomcat中自带了servlet-api,项目编译的时候会用到这个包,而项目发布的时候就不会用到这个包。
所以为了减少包导入时可能出现的冲突,maven引入了依赖范围这个概念,即我们上面提到的scope来解决这个问题。Maven中有主要有以下几种依赖范围:
compile:编译,测试,运行三种classpath都有效,会随着项目一起发布(如hibernate jar包)
test:测试范围有效,在编译打包、运行时都不会使用这个依赖(如junit jar包)
provided:编译和测试有效,最后不会随项目发布(如servlet-api)
runtime:测试和运行时依赖(如JDBC驱动)
system:测试和运行时依赖(如systemPath)
import :仅在类型为pom的依赖关系支持在部分
(注:关于间接依赖问题,依赖的jar包必须是compile范围,若是test范围,则发布的jar包不会包含test范围依赖的jar包和依赖关系。)
(3)传递性依赖
Maven的依赖是具有传递性的,能够自动加载我们引入的依赖包的依赖,而不必去手动指定,从而减少冲突,不用的功能不传递。
比如A->B,B->C,那么A间接的依赖于C,这就是依赖的传递性,其中A对于B是第一直接依赖,B对于C是第二直接依赖,C为A的传递性依赖。对于传递性依赖,依赖的范围如下表:
上图中可以发现一些规律。例如,第一直接依赖范围是Test,第二直接依赖范围是Compile,那么传递性依赖的范围就是Test;再如,第二直接依赖的范围是provided的时候,只传递第一直接依赖的范围也为provided的依赖,且传递性依赖的范围同样为provided。
(4)依赖版本的原则
依赖版本的原则,也称为依赖调节原则,是maven解决传递依赖时jar包冲突问题的方法。共有三种方法:
optional:在dependency元素下的optional设置,是一个boolean值,true表示可选依赖不会被传递。如A->B,B->C,B->D,A对B直接依赖,B对C和D是可选依赖,那么在A中不会引入C和D。
最短路径原则:现实中可能存在这样的情况,如A->B->C->D->Z(1.0),E->D->Z(2.0),此时Z出现了2个版本,maven会使用Z(2.0),因为其路径更近。
最先声明原则:如果路径相同,maven 默认配置在前面的优先使用,如A->B->Z(1.0),E->D->Z(2.0),则使用Z(1.0),因为Z(1.0)最先声明。
(5)排除依赖
传递性依赖极大的简化了项目依赖的管理,但有些时候这种特性也会带来问题,它可能会把我们不需要或过时且不安全的jar包,也引入到了工程当中,导致项目结构变得更复杂。因此,就需要使用依赖排除,解决jar冲突问题。
可以先在依赖这个项目的pom.xml中去除想排除的依赖,再添加指定版本的依赖。需要指明所包含坐标和排除依赖包中所包含的依赖关系,即groupId和artifactId。(注:不需要添加 version)
例如:
例子中使用exclusions元素排除了B->C依赖的传递,也就是B->C不会被传递到A中。如果想要排除多个,可以先然后再添加 <exclusion>。
四、Maven仓库与私服的介绍
4.1 Maven仓库
前面聊“mvn命令的执行过程”就有提到仓库,简单的说,Maven仓库就是存放依赖包的地方。Maven仓库可简单分为本地仓库和远程仓库两类:
本地仓库:指存于本机的仓库,默认地址为/C:\Users\用户\.m2,在setting.xml中的仓库目录位置指定,其中还包含了镜像、插件、代理等配置。
远程仓库:远程仓库又可以分为中央仓库和私服。中央仓库架设在Internet上,是一个大而全的包仓库,包含了世界上绝大多数流行的开源Java包,以及源码等信息。
4.2 私服的介绍
打游戏时经常会听到私服,在Maven中不一样,一般指公司内部的私服,或是使用者自己搭建的maven仓库。它设在局域网内,通过代理广域网上的远程仓库,供局域网内的用户使用。主要用于缓解频繁从外网下载jar包资源的压力、加速Maven构建等,从而高效的使用Maven,而且使用私库作为缓存层,也相对安全一些。
比如,成本上千的项目团队,都从远程仓库中把需要依赖的构件下载到本地仓库,那对公司的网络要求也比较高;再如,自己开发的一些jar包给其他组使用,如果没有私服,可能需要手动发给别人或者上传到共享机器中,管理起来不是很方便。
◎没有使用私服的仓库构件下载
建立自己的私服,有三种开源的专用于Maven仓库管理软件,可以用来帮助我们建立私服:
Apache基金会的Archiva
JFrog的Artifactory
Sonatype的Nexus(该款最流行)
◎使用私服的仓库构件下载
本地构建发布到私服,常见的方式如下:
使用maven部署构件至nexus私服
手动部署第三方构件至nexus私服(如短信发送商的jar包,这个包远程仓库是不存在的,我们要将这个包上传到私服供所有开发使用)
五、Maven的生命周期与插件
在Maven中有五大核心概念,除了前文已经总结过的坐标、依赖以及仓库之外,还有生命周期和插件。掌握了这些,也就基本上把握了Maven的命脉,其它Maven的知识主要是具体场景的应用,大体上都离不开这五大核心概念。
5.1 Maven的生命周期
人的生命周期有生、老、病、死;金融产品有潜在、兴起、成长、成熟和衰退;maven也不例外,它的生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署、站点生成等几乎所有的项目构建过程,而且易扩展、更规范。
maven的生命周期是抽象的,其实际行为都是由插件来完成的,因此maven的生命周期和插件两者协同工作,密不可分。这也正是maven的强大之处,这种思想与设计模式中的“模板方法”非常相似,既保证了Maven整体框架的轻便,也最大程度的扩展性。
maven有三套相互独立的生命周期,“相互独立”,其生命周期并不是一个整体。分别是clean、default和site:
clean:清理项目
default:构建项目(如编译,测试,打包等)
site:建立项目站点,发布站点。
5.2 生命周期详解
知道了每套生命周期的大概用途和相互关系以后,接下来逐个详细看一下每套生命周期。每套生命周期里包含一些阶段(phase),与日常中命令行的输入往往相对应。
以clean生命周期为例,它包含的阶段有pre-clean、clean和post-clean。当我们调用pre-clean的时候,只有pre-clean阶段后执行;当我们调用clean的时候,pre-clean和clean阶段会按顺序执行;当我们调用post-clean的时候,pre-clean、clean和post-clean都会按顺序执行。
clean生命周期包含了三个阶段:
pre-clean:执行需要在clean前完成的工作
clean:移除所有上一次构建生成的文件
post-clean:执行需要在clean后立刻完成的工作
site生命周期包含了以下阶段:
pre-site:执行些需要在生成站点文档之前完成的工作
site:生成项目的站点文档
post-site:执行需要在生成站点文档之后完成的工作,并且为部署做准备
site-deploy:将生成的站点文档部署到特定的服务器上
default是maven中最重要的部分,它构建的核心功能,绝大部分工作都发生在这个生命周期中。生命周期包含了以下阶段:
validate
generate-sources
process-sources
generate-resources
process-resources 复制并处理资源文件至目标目录,准备打包
compile 编译项目的源代码
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources 复制并处理资源文件至目标测试目录
test-compile 编译测试源代码
process-test-classes
test 使用合适的单元测试框架运行测试。测试代码不会被打包或部署
prepare-package
package 接受编译好的代码,打包成可发布的格式,如JAR
pre-integration-test
integration-test
post-integration-test
verify
install 将包安装至本地仓库,以让其它项目依赖
deploy 将最终的包复制到远程的仓库,以让其它开发人员与项目共享
(注:运行任何一个阶段的时候,它前面的所有阶段都会被运行,如 mvn clean调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的pre-clean和clean阶段。这也就是为什么我们运行mvn install的时候,代码会被编译,测试,打包)
5.3 Maven的插件
通过上面的生命周期我们可以了解到,生命周期中处理都是插件完成的,插件可以和maven生命周期的阶段进行绑定,也可以通过mvn命令的方式调用直接运行。总之,Maven中插件非常重要,必不可少。如下图:
maven中的插件以jar的方式存在于仓库中,和其他构件是一样的,也是通过坐标进行访问,插件中的每个功能就叫做插件的目标(Plugin Goal),每个插件中可能包含一个或者多个插件目标,即上图的每一个功能对应一个目标。
maven插件的安装机制就不细聊了,官方文档或文章有很多,这里就提一提插件绑定。插件绑定主要分为内置绑定和自定义绑定两大类。
内置绑定:指为了让maven开箱即用,maven开发了很多默认的插件来完成每个生命周期对于阶段的一些工作。(有兴趣的朋友可以去看源码,借鉴他们的设计思路,开发自己的插件)
自定义绑定:指为了完成更多个性化的任务,maven社区的大牛开发了很多的插件,比如自动打包并且发布到服务器然后重启服务器的插件;或者自动打包自动运行打包好的构件等。
以往在日常工作中,使用maven只是机械的执行配置或命令,对其中的原理与过程并无了解。近日接触了一帮刚做Java的兄弟,遇到问题后被问到一些细节答不上来。所以整理学习了下,希望对想要了解的maven的朋友们有所帮助。如果大家想全面掌握maven,推荐大家去看许晓斌写得《maven实战》。
完
参考书籍或文献:
1.许晓斌.《maven实战》,2011年
2.许晓斌.《Maven十年,来认识下》,2011年
3.AlanLee.《Maven系列文章》,2016年
4.路人甲.《Maven系列文章》,2018年
5.果冻想.《Maven系列文章》,2019年
点击查看近期推送