Maven大家都很熟悉,但是我们很多人,对它其实都是似乎很熟,但是又好像不熟悉的感觉,包括我,今天咱们就一起来彻底了解Maven的所有功能,我们从入门,到原理剖析,再到实践操作,最后是私服的搭建以及配置,整体并彻底了解一下Maven。
下载路径:Maven官网
上面一个是Linux环境,一个Windows环境,大家依照自己的环境下载解压即可(后面我会以Windows环境为例)。
在系统环境中,新建一个MAVEN_HOME或M2_HOME的环境变量,值写成解压路径。
找到Path变量并编辑,在其中新增一行,配置一下bin目录:
%M2_HOME%\bin
之所以要配置这一步,主要是因为软件的bin目录,通常会存放一些可执行的脚本/工具,如JDK的bin目录中,就存放着javac、javap、jstack……一系列工具。如果不在Path中配置bin,那想要使用这些工具,只能去到JDK安装目录下的bin目录,然后才能使用。
配置完毕以后就会全局生效。
找到根目录下的conf/settings.xml
,然后点击编辑,找到
标签,将其挪动到注释区域外,然后配置本地仓库位置:
<localRepository>空的本地目录(最好别带中文)localRepository>
由于Apache的官方镜像位于国外,平时拉取依赖比较慢,我们一般配置阿里的镜像仓库。搜索
标签:
<mirror>
<id>alimavenid>
<name>aliyun mavenname>
<url>http://maven.aliyun.com/nexus/content/groups/public/url>
<mirrorOf>centralmirrorOf>
mirror>
到这里,整个Maven安装流程全部结束,最后在终端工具,执行mvn -v命令,就可以看到Maven的版本信息。
安装好Maven完毕后,通过IDEA工具来创建Maven项目,我们需要配置本地Maven及仓库位置:
Maven仓库官网
有时候我们需要对引入的依赖进行范围管理,比如测试依赖包JUnit,我们只需要在我们进行单元测试的时候有效,其他环境不需要。这时候我们就可以通过
标签来控制。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>2.1.8.RELEASEversion>
<scope>testscope>
dependency>
作用范围有三种:
<dependency>
<groupId>some.groupgroupId>
<artifactId>some-artifactartifactId>
<version>1.0version>
<scope>pluginscope>
dependency>
这里的plugin就是自定义的scope,表示该依赖只在Maven插件中生效。
在Maven中,整体会分为工程、仓库两大类,工程是“依赖使用者”,仓库是“依赖提供者”,关系如下:
这里面:
中央仓库:指的是镜像源,里面拥有海量的公共jar包资源;
远程仓库:也就是私服仓库,主要存储公司内部的jar包资源;
本地仓库:自己电脑本地的仓库,会在磁盘上存储jar包资源。
Maven的工作流程如下:
在IDEA中我们可以看到Maven的九种Lifecycle命令,如下:
双击其中任何一个,都会执行相应的Maven构建动作,因为里面封装了Maven提供的命令。下面解释一下每个命令的作用:
Maven总共划分了三套生命周期:
主要看default,该生命周期涵盖了构建过程中的检测、编译、测试、打包、验证、安装、部署每个阶段。
要注意的是,同一个生命周期,执行当前命令的时候,前面的命令都会自动执行。
比如执行mvn test
命令,它会先执行validate、compile这两个阶段,然后才会真正执行test阶段。
多命令同时执行:
mvn clean install
执行过程:先执行clean周期里的pre-clean、clean,再执行default周期中,validate~install这个闭区间内的所有阶段。
还有个小问题点:
就是我们的IDEA为什么还会有个Plugins的,如下:
这是因为:Maven插件会实现生命周期中的每个阶段,而每个阶段具体执行的操作,这会交给插件去干。当你双击Lifecycle中的某个生命周期阶段,实际会调用Plugins中对应的插件。
依赖冲突是指:在Maven项目中,当多个依赖包,引入了同一份类库的不同版本时,可能会导致编译错误或运行时异常。这种情况下,想要解决依赖冲突,可以靠升级/降级某些依赖项的版本,从而让不同依赖引入的同一类库,保持一致的版本号,还可以通过隐藏依赖、或者排除特定的依赖项来解决问题。
在这个之前我们得要了解一下什么是依赖传递性。
依赖具有传递性,当我们引入了一个依赖的时候,就会自动引入该依赖引入的所有依赖,依次往下引入所有依赖。比如我引入spring-boot-starter-web,会自动引入:
总之就是当引入的一个包,如果依赖于其他包(类库),当前的工程就必须再把其他包引入进来。
在绝对大多数情况下,依赖冲突问题并不需要我们考虑,Maven工具会自动解决,根据如下几个原则:
Maven会依据上述三条原则,帮我们智能化自动剔除冲突的依赖。
所谓的排除依赖,即是指从一个依赖包中,排除掉它依赖的其他包,如果出现了Maven无法自动解决的冲突,就可以基于这种手段进行处理。如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.1.8.RELEASEversion>
<exclusions>
<exclusion>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
exclusion>
exclusions>
dependency>
分模块开发,即是指创建多个Maven工程,组成一个完整项目。通常会先按某个维度划分出多个模块,接着为每个模块创建一个Maven工程,比较常见的拆分维度有:
多模块开发的好处有:
聚合工程,即是指:一个项目允许创建多个子模块,多个子模块组成一个整体,可以统一进行项目的构建。
在此之前我们先理解一下父子工程:
父工程:不具备任何代码、仅有pom.xml的空项目,用来定义公共依赖、插件和配置;
子工程:编写具体代码的子项目,可以继承父工程的配置、依赖项,还可以独立拓展。
而Maven聚合工程,就是基于父子工程结构,来将一个完整项目,划分出不同的层次。
首先要创建一个空的Maven项目,作为父工程,这时可以在IDEA创建Maven项目时,把打包方式选成POM,也可以创建一个普通的Maven项目,然后把src目录删掉,再修改一下pom.xml:
<packaging>pompackaging>
然后创建子工程
继续往下走即可。
这时候我们看父工程的pom.xml中,会用一个
标签,来记录自己名下的子工程列表,而子工程的pom头,也多了一个
标签包裹,如下:
注意:子工程下面也可以继续创建子工程,但是不建议,这样会比较混乱。
一般情况下,为了防止依赖冗余,我们会将公共的依赖、配置、插件等,都可以配置在父工程里。然后会发现子工程都会继承了父依赖。
为了防止不同子工程引入不同版本的依赖,最好的做法是在父工程中,统一对依赖的版本进行控制,规定所有子工程都使用同一版本的依赖。可以使用
标签来管理。
要注意如下两个的区别:
:定义强制性依赖,写在该标签里的依赖项,子工程必须强制继承;
:定义可选性依赖,该标签里的依赖项,子工程可选择使用。
注意:子工程在使用
中已有的依赖项时,不需要写
版本号,版本号在父工程中统一管理,这就满足了前面的需求。好处在于:以后为项目的技术栈升级版本时,不需要修改每个子工程的POM,只需要修改父POM文件即可,提高了维护性。
大多数情况下,Maven会基于那三条原则,自动帮你剔除重复的依赖,如果无法剔除,就得使用“隐藏依赖”技术了,如下:
假如现有两个子工程001,002。
修改001的pom.xml,如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.8.RELEASEversion>
<optional>trueoptional>
dependency>
此时我们发现多了一个
标签,该标签即是“隐藏依赖”的开关:
true:开启隐藏,当前依赖不会向其他工程传递,只保留给自己用;
false:默认值,表示当前依赖会保持传递性,其他引入当前工程的项目会间接依赖。
当开启隐藏后,其他工程引入当前工程时,就不会再间接引入当前工程的隐藏依赖,因此来手动排除聚合工程中的依赖冲突问题。
有一个问题,比如项目需要用到Spring-Cloud-Alibaba的多个依赖项,如Nacos、Sentinel……等,根据前面所说的原则,由于这些依赖项可能会在多个子工程用到,最好的方式是定义在父POM的
标签里,可是CloudAlibaba依赖这么多,一个个写未免太繁杂、冗余了吧?
如下:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
标签为import,通常用在聚合工程的父工程中,不过必须配合
使用,表示把spring-cloud-alibaba-dependencies
的所有子依赖,作为当前项目的可选依赖向下传递。而当前父工程下的所有子工程,在继承父POM时,也会将这些可选依赖继承过来。
Maven聚合工程可以对所有子工程进行统一构建。
尾巴上带有root标识的工程,意味这是一个父工程,当你双击父工程的某个Lifecycle命令,它找到父POM的
标签,再根据其中的子工程列表,完成对整个聚合工程的构建工作。
这里就有一个问题,我们怎么解决maven子工程之间的相互依赖,这个随着项目的复杂度提升,引用不当,就会出现,最好的办法就是解决这种问题,因为这种本质是不合理的,有如下解决方式:
有时候我们不想执行测试用例,但是如果直接双击父工程里的package命令,但test命令在package之前,按照之前聊的生命周期原则,就会先执行test,再进行打包。
解决办法:
先选中test命令,接着点击上面的闪电图标,这时test就会画上横线,表示该阶段会跳过。
同时还可以在pom.xml里,配置插件来精准控制,比如跳过某个测试类不执行,配置规则如下:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-pluginartifactId>
<version>2.22.1version>
<configuration>
<skipTests>trueskipTests>
<includes>
<include>**/XXX*Test.javainclude>
includes>
<excludes>
<exclude>**/XXX*Test.javaexclude>
excludes>
configuration>
plugin>
plugins>
build>
我们可以通过在POM的
标签中,自定义属性,来进行版本管理,如下:
实际工作会分为开发、测试、生产等环境,不同环境的配置信息也略有不同,而大家都知道,我们可以通过spring.profiles.active属性,来动态使用不同环境的配置,而Maven为何又整出一个多环境配置出来呢?
带着问题我们先搭建一个SpringBoot版的Maven聚合工程。
首先创建一个只有POM的父工程。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.5.RELEASEversion>
<relativePath/>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.zhuzigroupId>
<artifactId>maven_zhuziartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
<properties>
<java.version>8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
接着来创建子工程。
<parent>
<artifactId>maven_zhuziartifactId>
<groupId>com.zhuzigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>boot_zhuzi_001artifactId>
<name>boot_zhuzi_001name>
<description>Demo project for Spring Bootdescription>
然后再做Maven多环境配置,找到父工程的POM进行修改,如下:
<profiles>
<profile>
<id>devid>
<properties>
<profile.active>devprofile.active>
properties>
profile>
<profile>
<id>prodid>
<properties>
<profile.active>prodprofile.active>
properties>
<activation>
<activeByDefault>trueactiveByDefault>
activation>
profile>
<profile>
<id>testid>
<properties>
<profile.active>testprofile.active>
properties>
profile>
profiles>
配置完这个后,刷新当前Maven工程,IDEA中就会出现这个:
默认是prod,因为POM中用
标签做了指定,然后在子工程的application.yml
中,完成Spring的多环境配置,如下:
# 设置启用的环境
spring:
profiles:
active: ${profile.active}
---
# 开发环境
spring:
profiles: dev
server:
port: 80
---
# 生产环境
spring:
profiles: prod
server:
port: 81
---
# 测试环境
spring:
profiles: test
server:
port: 82
---
或者采用不同环境的yml来做区分配置信息。这时候会发现spring.profiles.active
属性以前我们会写上固定的值,而现在写的是${profile.active}
。这表示会从pom.xml中,读取profile.active
属性值,而父POM中配了三组值:dev、prod、test
,所以当前子工程的POM,也会继承这组配置,而目前默认勾选在prod上,所以最终表达的含义是spring.profiles.active=prod
。
要注意的是,要想yml读取到pom文件的值,我们还得在父pom加一个依赖和插件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<version>2.1.5.RELEASEversion>
<optional>trueoptional>
dependency>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<version>3.2.0version>
<configuration>
<encoding>UTF-8encoding>
<useDefaultDelimiters>trueuseDefaultDelimiters>
configuration>
plugin>
最后我们来启动子工程,操作流程如下:
底层它的执行流程是这样:
${profile.active}
会读到profile.active=dev
,使用dev配置组;application.yml
中的dev配置组,server.port=80
,所以最终通过80端口启动。有人可能就要问,既然都是修改文件启动,这不就是从修改yml改成了修改pom呗,这样想就错了,因为你们肯定也发现了,我从头到尾一直是在父工程上的pom,修改环境配置,只需要修改一次,假如现在有20个微服务,如果去修改yml,就得每个都得修改,那还不把人累死,而修改父工程pom,只需要修改一次。
今天和大家就分享到这里吧,明天给大家分享下Maven私服的搭建过程,内容有点多,分两次写吧。