让我们彻底了解Maven(一)--- 基础和进阶

Maven大家都很熟悉,但是我们很多人,对它其实都是似乎很熟,但是又好像不熟悉的感觉,包括我,今天咱们就一起来彻底了解Maven的所有功能,我们从入门,到原理剖析,再到实践操作,最后是私服的搭建以及配置,整体并彻底了解一下Maven。

1. Maven的安装以及配置

1.1 Maven的下载

下载路径:Maven官网
在这里插入图片描述
上面一个是Linux环境,一个Windows环境,大家依照自己的环境下载解压即可(后面我会以Windows环境为例)。

1.2 环境变量的配置

在系统环境中,新建一个MAVEN_HOME或M2_HOME的环境变量,值写成解压路径。
找到Path变量并编辑,在其中新增一行,配置一下bin目录:

%M2_HOME%\bin

之所以要配置这一步,主要是因为软件的bin目录,通常会存放一些可执行的脚本/工具,如JDK的bin目录中,就存放着javac、javap、jstack……一系列工具。如果不在Path中配置bin,那想要使用这些工具,只能去到JDK安装目录下的bin目录,然后才能使用。
配置完毕以后就会全局生效。

1.3 指定Maven本地仓库位置

找到根目录下的conf/settings.xml,然后点击编辑,找到标签,将其挪动到注释区域外,然后配置本地仓库位置:

<localRepository>空的本地目录(最好别带中文)localRepository>

1.4 配置阿里镜像仓库

由于Apache的官方镜像位于国外,平时拉取依赖比较慢,我们一般配置阿里的镜像仓库。搜索标签:

<mirror>  
    <id>alimavenid>  
    <name>aliyun mavenname>  
    <url>http://maven.aliyun.com/nexus/content/groups/public/url>
    <mirrorOf>centralmirrorOf>          
mirror>

到这里,整个Maven安装流程全部结束,最后在终端工具,执行mvn -v命令,就可以看到Maven的版本信息。

1.5 Maven的IDEA配置

安装好Maven完毕后,通过IDEA工具来创建Maven项目,我们需要配置本地Maven及仓库位置:
让我们彻底了解Maven(一)--- 基础和进阶_第1张图片

1.6 Maven依赖的查询网站

Maven仓库官网

1.7 依赖的范围管理

有时候我们需要对引入的依赖进行范围管理,比如测试依赖包JUnit,我们只需要在我们进行单元测试的时候有效,其他环境不需要。这时候我们就可以通过标签来控制。

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <version>2.1.8.RELEASEversion>
    <scope>testscope>
dependency>

作用范围有三种:

  1. 主代码范围有效。
  2. 测试代码范围有效。
  3. 是否参与打包(package命令范围内)
    让我们彻底了解Maven(一)--- 基础和进阶_第2张图片
    同时,标签还可以通过自定义的方式来添加其他的scope范围,例如Maven插件中使用的scope值:
<dependency>
    <groupId>some.groupgroupId>
    <artifactId>some-artifactartifactId>
    <version>1.0version>
    <scope>pluginscope>
dependency>

这里的plugin就是自定义的scope,表示该依赖只在Maven插件中生效。

2. Maven的工作原理

在Maven中,整体会分为工程、仓库两大类,工程是“依赖使用者”,仓库是“依赖提供者”,关系如下:
让我们彻底了解Maven(一)--- 基础和进阶_第3张图片
这里面:
中央仓库:指的是镜像源,里面拥有海量的公共jar包资源;
远程仓库:也就是私服仓库,主要存储公司内部的jar包资源;
本地仓库:自己电脑本地的仓库,会在磁盘上存储jar包资源。

Maven的工作流程如下:

  1. 项目通过GAV坐标引入依赖,首先会去本地仓库查找jar包;
  2. 如果在本地仓库中找到了,直接把依赖载入到当前工程的External Libraries中;
  3. 如果没找到,则去读取settings.xml文件,判断是否存在私服配置;
  4. 如果有私服配置,根据配置的地址找到远程仓库,接着拉取依赖到本地仓库;
  5. 如果远程仓库中没有依赖,根据私服配置去中央仓库拉取,然后放到私服、本地仓库;

3. Maven的生命周期

在IDEA中我们可以看到Maven的九种Lifecycle命令,如下:
让我们彻底了解Maven(一)--- 基础和进阶_第4张图片
双击其中任何一个,都会执行相应的Maven构建动作,因为里面封装了Maven提供的命令。下面解释一下每个命令的作用:

  • clean:清除当前工程编译后生成的文件(即删除target整个目录);
  • validate:对工程进行基础验证,如工程结构、pom、资源文件等是否正确;
  • compile:对src/main/java目录下的源码进行编译(会生成target目录);
  • test:编译并执行src/test/java/目录下的所有测试用例
  • package:将当前项目打包,普通项目打jar包,webapp项目打war包;
  • verify:验证工程所有代码、配置进行是否正确,如类中代码的语法检测等;
  • install:将当前工程打包,然后安装到本地仓库,别人可通过GAV导入;
  • site:生成项目的概述、源码测试覆盖率、开发者列表等站点文档(需要额外配置);
  • deploy:将当前工程对应的包,上传到远程仓库,提供给他人使用(私服会用)。

Maven总共划分了三套生命周期:
让我们彻底了解Maven(一)--- 基础和进阶_第5张图片
主要看default,该生命周期涵盖了构建过程中的检测、编译、测试、打包、验证、安装、部署每个阶段。

要注意的是,同一个生命周期,执行当前命令的时候,前面的命令都会自动执行。
比如执行mvn test命令,它会先执行validate、compile这两个阶段,然后才会真正执行test阶段。

多命令同时执行:

mvn clean install

执行过程:先执行clean周期里的pre-clean、clean,再执行default周期中,validate~install这个闭区间内的所有阶段。

还有个小问题点:
就是我们的IDEA为什么还会有个Plugins的,如下:
让我们彻底了解Maven(一)--- 基础和进阶_第6张图片
这是因为:Maven插件会实现生命周期中的每个阶段,而每个阶段具体执行的操作,这会交给插件去干。当你双击Lifecycle中的某个生命周期阶段,实际会调用Plugins中对应的插件。

4. Maven的高级操作

4.1 依赖冲突

依赖冲突是指:在Maven项目中,当多个依赖包,引入了同一份类库的不同版本时,可能会导致编译错误或运行时异常。这种情况下,想要解决依赖冲突,可以靠升级/降级某些依赖项的版本,从而让不同依赖引入的同一类库,保持一致的版本号,还可以通过隐藏依赖、或者排除特定的依赖项来解决问题。

在这个之前我们得要了解一下什么是依赖传递性。

1. 依赖的传递性

依赖具有传递性,当我们引入了一个依赖的时候,就会自动引入该依赖引入的所有依赖,依次往下引入所有依赖。比如我引入spring-boot-starter-web,会自动引入:
让我们彻底了解Maven(一)--- 基础和进阶_第7张图片
让我们彻底了解Maven(一)--- 基础和进阶_第8张图片
总之就是当引入的一个包,如果依赖于其他包(类库),当前的工程就必须再把其他包引入进来。

2. 自动解决冲突

在绝对大多数情况下,依赖冲突问题并不需要我们考虑,Maven工具会自动解决,根据如下几个原则:

  1. 层级优先原则:Maven会根据依赖树的层级,来自动剔除相同的包,层级越浅,优先级越高,无效的包会自动变成灰色。
  2. 声明优先原则:上条原则是基于层级深度,来自动剔除冲突的依赖,假设同级出现两个相同的依赖,这时候同层级出现包冲突时,先声明的会覆盖后声明的,为此后者会被剔除。
  3. 配置优先原则:同级出现不同版本的相同类库时,后配置的会覆盖先配置的。

Maven会依据上述三条原则,帮我们智能化自动剔除冲突的依赖。

3. 主动排除依赖

所谓的排除依赖,即是指从一个依赖包中,排除掉它依赖的其他包,如果出现了Maven无法自动解决的冲突,就可以基于这种手段进行处理。如下:

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <version>5.1.8.RELEASEversion>
    <exclusions>
        
        <exclusion>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-beansartifactId>
        exclusion>
    exclusions>
dependency>

4.2 Maven的分模块开发

分模块开发,即是指创建多个Maven工程,组成一个完整项目。通常会先按某个维度划分出多个模块,接着为每个模块创建一个Maven工程,比较常见的拆分维度有:

  • 接入维度:按不同的接入端,将项目划分为多个模块,如APP、WEB、小程序等;
  • 业务维度:根据业务性质,将项目划分为多个业务模块,如前台、后台、用户等;
  • 功能维度:共用代码做成基础模块,业务做成一个模块、API做成一个模块……。

多模块开发的好处有:

  • 简化项目管理,拆成多个模块后,每个模块可以独立编译、打包、发布等;
  • 提高代码复用性,不同模块间可以相互引用,可以建立公共模块,减少代码冗余度;
  • 方便团队协作,多人各司其职,负责不同的模块,Git管理时也能减少交叉冲突;
  • 构建管理度更高,更方便做持续集成,可以根据需要灵活配置整个项目的构建流程;

4.3 Maven聚合工程

聚合工程,即是指:一个项目允许创建多个子模块,多个子模块组成一个整体,可以统一进行项目的构建。
在此之前我们先理解一下父子工程:
父工程:不具备任何代码、仅有pom.xml的空项目,用来定义公共依赖、插件和配置;
子工程:编写具体代码的子项目,可以继承父工程的配置、依赖项,还可以独立拓展。

而Maven聚合工程,就是基于父子工程结构,来将一个完整项目,划分出不同的层次。

1. 聚合工程的创建

首先要创建一个空的Maven项目,作为父工程,这时可以在IDEA创建Maven项目时,把打包方式选成POM,也可以创建一个普通的Maven项目,然后把src目录删掉,再修改一下pom.xml:


<packaging>pompackaging>

让我们彻底了解Maven(一)--- 基础和进阶_第9张图片

然后创建子工程
让我们彻底了解Maven(一)--- 基础和进阶_第10张图片
让我们彻底了解Maven(一)--- 基础和进阶_第11张图片
继续往下走即可。
让我们彻底了解Maven(一)--- 基础和进阶_第12张图片
这时候我们看父工程的pom.xml中,会用一个标签,来记录自己名下的子工程列表,而子工程的pom头,也多了一个标签包裹,如下:
让我们彻底了解Maven(一)--- 基础和进阶_第13张图片
让我们彻底了解Maven(一)--- 基础和进阶_第14张图片

注意:子工程下面也可以继续创建子工程,但是不建议,这样会比较混乱。

2. 聚合工程的依赖管理

一般情况下,为了防止依赖冗余,我们会将公共的依赖、配置、插件等,都可以配置在父工程里。然后会发现子工程都会继承了父依赖。
为了防止不同子工程引入不同版本的依赖,最好的做法是在父工程中,统一对依赖的版本进行控制,规定所有子工程都使用同一版本的依赖。可以使用标签来管理。

要注意如下两个的区别:
:定义强制性依赖,写在该标签里的依赖项,子工程必须强制继承;
:定义可选性依赖,该标签里的依赖项,子工程可选择使用。

注意:子工程在使用中已有的依赖项时,不需要写版本号,版本号在父工程中统一管理,这就满足了前面的需求。好处在于:以后为项目的技术栈升级版本时,不需要修改每个子工程的POM,只需要修改父POM文件即可,提高了维护性。

3. 聚合工程处理依赖冲突

大多数情况下,Maven会基于那三条原则,自动帮你剔除重复的依赖,如果无法剔除,就得使用“隐藏依赖”技术了,如下:
假如现有两个子工程001,002。
修改001的pom.xml,如下:

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>5.1.8.RELEASEversion>
    <optional>trueoptional>
dependency>

此时我们发现多了一个标签,该标签即是“隐藏依赖”的开关:
true:开启隐藏,当前依赖不会向其他工程传递,只保留给自己用;
false:默认值,表示当前依赖会保持传递性,其他引入当前工程的项目会间接依赖。

当开启隐藏后,其他工程引入当前工程时,就不会再间接引入当前工程的隐藏依赖,因此来手动排除聚合工程中的依赖冲突问题。

4. 父工程的依赖传递

有一个问题,比如项目需要用到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,通常用在聚合工程的父工程中,不过必须配合pom使用,表示把spring-cloud-alibaba-dependencies的所有子依赖,作为当前项目的可选依赖向下传递。而当前父工程下的所有子工程,在继承父POM时,也会将这些可选依赖继承过来。

5. 聚合工程的构建

Maven聚合工程可以对所有子工程进行统一构建。
让我们彻底了解Maven(一)--- 基础和进阶_第15张图片
尾巴上带有root标识的工程,意味这是一个父工程,当你双击父工程的某个Lifecycle命令,它找到父POM的标签,再根据其中的子工程列表,完成对整个聚合工程的构建工作。
在这里插入图片描述

这里就有一个问题,我们怎么解决maven子工程之间的相互依赖,这个随着项目的复杂度提升,引用不当,就会出现,最好的办法就是解决这种问题,因为这种本质是不合理的,有如下解决方式:

  1. 将相互依赖的部分抽出来,单独形成一个模块,让这两个来分别依赖这个模块。
  2. 使用插件build-helper-maven-plugin。

6. 聚合打包跳过测试

有时候我们不想执行测试用例,但是如果直接双击父工程里的package命令,但test命令在package之前,按照之前聊的生命周期原则,就会先执行test,再进行打包。
解决办法:
让我们彻底了解Maven(一)--- 基础和进阶_第16张图片
先选中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>

4.4 Maven属性

我们可以通过在POM的标签中,自定义属性,来进行版本管理,如下:
让我们彻底了解Maven(一)--- 基础和进阶_第17张图片
让我们彻底了解Maven(一)--- 基础和进阶_第18张图片

4.5 Maven的多环境配置

实际工作会分为开发、测试、生产等环境,不同环境的配置信息也略有不同,而大家都知道,我们可以通过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中就会出现这个:
让我们彻底了解Maven(一)--- 基础和进阶_第19张图片
默认是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>

最后我们来启动子工程,操作流程如下:

  1. 在Maven工具的Profiles中勾选dev,并刷新当前项目;
  2. 接着找到子工程的启动类,并右键选择Run启动子项目。

底层它的执行流程是这样:

  • 启动时,pom.xml根据勾选的Profiles,使用相应的dev环境配置;
  • yml中${profile.active}会读到profile.active=dev,使用dev配置组;
  • application.yml中的dev配置组,server.port=80,所以最终通过80端口启动。

有人可能就要问,既然都是修改文件启动,这不就是从修改yml改成了修改pom呗,这样想就错了,因为你们肯定也发现了,我从头到尾一直是在父工程上的pom,修改环境配置,只需要修改一次,假如现在有20个微服务,如果去修改yml,就得每个都得修改,那还不把人累死,而修改父工程pom,只需要修改一次。

今天和大家就分享到这里吧,明天给大家分享下Maven私服的搭建过程,内容有点多,分两次写吧。

你可能感兴趣的:(后台,maven,java,intellij-idea)