cover_image

Maven原理与实践

蜘蛛侠 缦图coder
2024年01月31日 06:10

图片

点击关注上方蓝字,阅读更多干货~


图片


缦图导读



本期介绍基于Java平台的项目管理和整合工具——Maven。作者将结合项目应用带领大家更为全面的认识与使用Maven,主要围绕Maven项目实战展开详细介绍。下面我们就如何配置Maven、如何分模块、如何解决冲突、如何配置私服、如何多环境配置等问题进行解答。希望大家通过阅读本文能有所帮助或者启发。

  • 前言

  • 1 什么是Maven?
    • 1.1  Maven简介
    • 1.2 为什么选择Maven
  • 2 Maven的主要功能
    • 2.1  Maven常用命令
    • 2.2 Maven生命周期
    • 2.3 Maven坐标详解
    • 2.4 Maven依赖管理
  • 3 Maven进阶
    • 3.1 分模块开发与设计
    • 3.2 聚合与继承
    • 3.3 属性管理
    • 3.4 私服
    • 3.5 多环境配置与应用
  • 4 总结


前言

自Java诞生以来,打包、测试、部署Java工程就一直是困扰Java程序员的枷锁。即使Ant出现,程序员仍深受困扰,并且Ant中Xml格式的Build文件的方式也再次加重了程序员的负担,直到有了Maven的出现。


下文将带大家一起了解什么是Maven,以及它的用途和基本功能,再一起看看我们实际项目当中经常遇到的一些问题和它们的解决方法。如果你没用过Maven,但有Make、Ant以及其他的构建工具的使用经验,相信通过下面的介绍你能更清楚地了解各种工具的优劣,并且会对Maven有一个全面的认识。

1.什么是Maven?

1.1 Maven简介

Apache Maven是一个项目管理和构建的工具,它基于项目对象模型(POM)的概念,通过一小段信息描述来管理项目的构建、报告和文档。

图片

图1 Maven模型图


1.2 为什么选择Maven

在maven未出世前,我们经常会遇到以下问题:
  • 如果jar包都到官方网站进行下载,会浪费很多时间,而且jar包可能不全

  • 当一个jar包依赖的其他jar包没导入到项目时,会导致项目运行不起来。而有些时候,我们根本无法判断一个jar包依赖了哪些其他的jar包
  • 项目的jar包需要复制和粘贴到WEB-INF/lib下。同样的jar包重复出现在不同的工程中,会造成空间的浪费,同时也让工程变得臃肿
  • 在划分模块时,我们都是使用package来进行划分。当项目很大,有很多子模块时,即使用package来进行划分,也会让人眼花缭乱

针对以上问题,目前最流行的解决方式就是maven,那么下面我们一起来具体看看。


2.Maven主要功能

2.1 Maven 常用命令

  • validate(校验):校验项目是否正确并且所有必要的信息可以完成项目的构建过程

  • initialize(初始化):初始化构建状态,比如:设置属性值

  • compile(编译) :编译项目的源代码

  • test(测试):使用合适的单元测试框架运行测试(Juint是其中之一)

  • package(打包):将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件

  • install(安装):安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖

  • deploy(部署):将最终的项目包复制到远程仓库中与其他开发者和项目共享

2.2 Maven生命周期

  • Maven构建项目生命周期描述的是一次构建过程经历了多少个事件

  • Maven对项目构建的生命周期划分为3套

    • clean:清理工作

    • default:核心工作,例如编译、测试、打包、安装等

    • site:产生报告、发布站点等

  • 同一生命周期内,执行后边的命令时,前边的所有命令会自动执行

图片

图2 Maven生命周期执行图

2.3 Maven坐标详解

  • 坐标是什么

    • Maven中的坐标是资源的唯一标识

    • 使用坐标来定义项目或引入项目中需要的依赖

  • Maven坐标主要组成

    • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.test)

    • artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)

    • version:定义当前项目版本号

    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-webmvc</artifactId>        <version>4.3.22.RELEASE</version>    </dependency>


2.4 依赖管理

2.4.1 依赖

依赖指当前项目运行所需的jar,一个项目可以设置多个依赖

    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-webmvc</artifactId>        <version>4.3.22.RELEASE</version>    </dependency>    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.3.12</version>        <scope>test</scope>    </dependency>


2.4.2 依赖范围

  • 通过设置坐标的依赖范围(scope),可以设置对应jar包的作用范围:编译环境、测试环境、运行环境

  • <scope>默认值:compile

     <dependency>          <groupId>junit</groupId>          <artifactId>junit</artifactId>          <scope>test</scope>     </dependency>
图片

图3 坐标依赖范围图


2.4.3 依赖传递性

  • 直接依赖:在当前项目中通过依赖配置建立的依赖关系

  • 间接依赖被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源

图片

图4 依赖关系图


2.4.4 依赖原则

  • 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高

  • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的

  • 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的

图片

图5 依赖原则图


2.4.5 依赖排除两种方式
  • 可选依赖指对外隐藏当前所依赖的资源——不透明

    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性>        <optional>true</optional>    </dependency>
这里的optional元素设置为true表示何意?

optional是Maven依赖jar时的一个选项,表示该依赖是可选的,项目之间依赖不传递。假设有两个项目A和B,其中A为父项目,B为子项目,在父项目中引入了Junit的依赖:

  • optional元素默认值(false)

当父项目添加Junit依赖时,并未添加optional选项,也就是默认了optional元素的值为false,便具有依赖传递性。此时,子项目B中会直接引入父项目A中引入的Junit的jar包,也就是说B项目打包时,jar/war包中会包含junit的jar包。

  • optional元素为true

当父项目引入Junit依赖时,设置optional元素为true,那么子项目B便有了更多的选择。如果项目B不需要Junit的jar包,那么在其pom文件中不需进行任何处理便可以。如果B项目也需要对应的jar包依赖,可以有两种选择:一种是将A项目中对应依赖的optional设置为false或去掉;另一种是在B项目中直接引入需要的该依赖。

  • 排除依赖指主动断开依赖的资源,被排除的资源无需指定版本——不需要

    <dependency>        <groupId>springframework</groupId>        <artifactId>spring-webmvc</artifactId>        <version>1.2.6</version>        <exclusions>            <exclusion>                <groupId>springframework</groupId>                <artifactId>spring-context</artifactId>            </exclusion>        </exclusions>    </dependency>
  • 排除依赖资源仅指定groupId,artifactId即可,无需指定version


2.4.6 依赖冲突

我们可以通过上面说明的依赖排除方式来分析更好解决冲突的方法,当然我们也可以使用idea插件提示。

排查依赖冲突的常用方法:

  • 常用工具idea中下载插件Maven Helper后在pom文件中可以查看依赖分析

  • 可以通过命令行的方式查看依赖树:mvn dependency:tree

图片
图6 依赖冲突排除方法图


3.Maven进阶

3.1 分模块开发与设计

分模块开发的意义:将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享

3.1.1 按照功能拆分

我们现在的项目体量往往比较大,这样会出现分工不明确,划分不清晰,后期不易维护的问题。以我们公司项目中一个卡券中心为例:

  • 立减券

  • 产品体验券

  • 实体礼品卡

卡券中心有多种类型的券,上面是目前最常用的三种券,如果非要把三个场景的代码放入到一个模块,那么当其中某一个模块代码出现问题,就会导致整个项目无法正常启动,从而导致公司的多个业务都无法正常运行,所以我们会按照功能将项目进行拆分。


3.1.2 按照模块拆分

像公司的订单、商品模块,订单中需要包含商品的详细信息,所以我们需要商品的模型类,而商品模块也会用到商品的模型类,这个时候如果两个模块中都写模型类,就会出现重复代码,会导致后期的维护成本较高。此时,我们就产生了将它们公共的部分抽取成一个独立的模块的想法,当其他模块需要使用时,可以像添加第三方jar包依赖一样来使用我们自己抽取的模块。这样就解决了代码重复的问题,这里我们就使用了按照模块拆分的方式。

3.2 聚合与继承

3.2.1 聚合

  • 聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
  • 工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)
  • 作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
  • 当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量模块同步构建的问题


3.2.2 聚合工程开发

  • 创建Maven模块,设置打包类型为pom<packaging>pom</packaging>
  • 每个maven工程都有对应的打包方式,默认为jar;web工程打包方式为war


3.2.3 设置当前聚合工程所包含的子模块名称

  • 聚合工程中所包含的模块在进行构建时会根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关

  • 参与聚合的工程无法向上感知是否参与聚合,只能向下配置哪些模块参与本工程的聚合

<modules>    <module>test-admin</module>    <module>test-facade</module></modules>

3.2.4 继承

  • 概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承

  • 作用:简化配置,减少版本冲突


3.2.5 继承关系

  • 创建Maven模块,设置打包类型为pom

  • 在父工程的pom文件中配置依赖关系(子工程将沿用父工程中的依赖关系)

  • 配置子工程中可选的依赖关系

  • 在子工程中配置当前工程所继承的父工程

  • 在子工程中配置使用父工程中可选依赖的坐标


3.2.6 聚合与继承的区别

  • 作用:

    • 聚合用于快速构建项目

    • 继承用于快速配置

  • 相同点:

    • 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中

    • 聚合与继承均属于设计型模块,并无实际的模块内容

  • 不同点:

    • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些

    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己


3.3 属性管理

在开发过程中我们会经常需要更新jar包的版本,尤其是分模块开发。父pom与子pom都有引用,这样进行改动就有可能出现漏改,导致程序出问题,并且改动相对比较复杂。这里就可以参考我们java基础所学习的变量,声明一个变量,在其他地方使用该变量,当变量的值发生变化后,所有使用变量的地方就会跟着自动修改。


3.3.1 常用属性分类

图片

图7 常用属性分类图


3.3.2 pom文件中最常用的就是自定义属性

  • 定义属性

    <properties>        <spring.mybatis.version>2.2.2</spring.mybatis.version>    </properties>
  • 引用属性 

    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>${spring.mybatis.version}</version>    </dependency>


3.4 私服

3.4.1 私服简介

私服是一个特殊的远程仓库,它是架设在局域网内的仓库服务。私服代理广域网上的远程仓库,仅供局域网内的Maven用户使用。当Maven需要下载构建的使用,它先从私服进行请求,如果私服上没有则从外部的远程仓库下载,然后缓存在私服上,再为Maven的下载请求提供服务。


3.4.2 为什么要使用私服

  • 缓存Maven中央仓库的jar包,当本地仓库没有jar包就无需到中央仓库下载,而是到私服下载。
  • 如果公司无法上网,那如何连接中央仓库呢?只需连接私服,而私服可以连网到中央仓库
  • 方便公司内部不同团队或者项目共享jar包,当需要共享jar包时,可以上传到私服,通过私服共享

3.4.3 仓库管理

  • 本地仓库:自己计算机上的一个目录

  • 中央仓库:由Maven团队维护的全球唯一的仓库

  • 地址:https://repo1.maven.org/maven2/

  • 远程仓库(私服):一般由公司团队搭建的私有仓库,当项目中使用坐标引入对应依赖jar包后,首先会查找本地仓库中是否有对应的jar包

    • 如果有,则在项目直接引用;

    • 如果没有,则去中央仓库中下载对应的jar包到本地仓库

  • 还可以搭建远程仓库,将来jar包的查找顺序则变为:本地仓库->远程仓库->中央仓库

图片

图8 仓库关系图

仓库分为三种:proxy代理仓库,group分组管理仓库,hosted本地发布仓库

图片

图9 仓库分类图


图片

图10 Maven工作流程图

一般常用Nexus来管理Maven仓库,创建三种类型的包:

图片

图11 仓库分类图


3.4.4 本地仓库访问私服权限与地址设置

  •  私服权限配置

 <servers>      <server>          <id>java</id>          <username>admin</username>          <password>test</password>      </server>      <server>          <id>releases</id>          <username>admin</username>          <password>test</password>      </server>      <server>          <id>snapshots</id>          <username>admin</username>          <password>test</password>      </server>  </servers>

  • 私服地址设置

 <mirrors>      <mirror>          <!--配置仓库组的ID-->          <id>java</id>          <!--repo代表repo仓库从私服获取-->          <mirrorOf>repo</mirrorOf>          <!--镜像名称-->          <name>java-public</name>          <url>镜像url</url>      </mirror>  </mirrors>

需要注意的是,server的id需要和配置的发布仓库Id保持一致,发布仓库和验证信息是通过id进行匹配的。


  • mirrorOf字段的配置
* 表示拦截所有请求,使用该仓库external:*  表示本地仓库的中没有的依赖才会使用该镜像仓库central  表示拦截去中央仓库的请求,使用该镜像仓库*,!repo 除repo这个仓库外,其他依赖都是用该镜像仓库repo 表示只有repo的仓库才会使用该镜像仓库
repo代表repo仓库内容都从私服获取,那么其他请求会到远程中央仓库去下载,但我们去远程中央仓库下载的时候我们会发现其速度非常慢。毕竟中央仓库在国外,连接超时是很正常的事情,这里我们就可以通过选用阿里云的镜像来解决这个问题。


  • maven配置阿里云镜像

  <mirrors>      <mirror>          <!--配置仓库组的ID-->          <id>java</id>          <!--repo代表repo仓库从私服获取-->          <mirrorOf>repo</mirrorOf>          <!--镜像名称-->          <name>java-public</name>          <url>镜像url</url>      </mirror>      <mirror>         <id>alimaven</id>         <name>aliyun maven</name>         <url>http://maven.aliyun.com/nexus/content/groups/public/</url>         <mirrorOf>central</mirrorOf>       </mirror>   </mirrors>

3.4.5 工程上传到私服服务器设置

在项目的pom文件中增加配置,然后通过mvn deploy进行部署

   <distributionManagement>        <!-- 定义releases库的坐标 -->        <repository>            <id>releases</id>            <name>Nexus Release Repository</name>            <url>仓库java-releases的访问路径</url>        </repository>        <!-- 定义snapshots库 -->        <snapshotRepository>            <id>snapshots</id>            <name>Nexus Snapshot Repository</name>            <url>仓库java-snapshots的访问路径/</url>        </snapshotRepository>    </distributionManagement>


3.4.6 私服访问中央服务器设置

  • 配置位置(nexus服务器页面设置)

图片

图12 Nexus配置中央仓库地址图


3.5 多环境配置与应用

我们日常都是在自己的开发环境进行开发,当开发完成后,把开发的功能部署到测试环境供测试人员进行测试使用;等测试人员测试通过后,我们会将项目部署到生成环境上线使用。这时就会产生一个问题,不同环境引入包的版本是不一致的,测试环境一般是SNAPSHOT版本,上线后需要去除SNAPSHOT。那么我们在项目中应如何配置?要想实现不同环境之间的配置切换又该如何来实现呢?下面我们就通过定义多环境来解决以上问题。


  • 使用多环境(构建过程):mvn 指令–P环境定义id
    <!--定义多环境-->    <profiles>        <!--定义具体的环境:开发环境-->        <profile>            <!--定义环境对应的唯一名称-->            <id>test</id>            <!--定义环境中专用的属性值-->            <properties>                <test-version>1.0.0-SNAPSHOT</test-version>                <rely-jar-suffix>-SNAPSHOT</rely-jar-suffix>                <deploy-repository-id>仓库的ID</deploy-repository-id>                <deploy-url>仓库的url</deploy-url>            </properties>
<activation> <activeByDefault>true</activeByDefault> </activation> </profile> <!--定义具体的环境:生产环境--> <profile> <id>prod</id> <properties> <test-version>1.0.0</test-version> <rely-jar-suffix></rely-jar-suffix> <deploy-repository-id>仓库的ID</deploy-repository-id> <deploy-url>仓库的url</deploy-url> </properties> </profile> </profiles>


4.总结

以上就是本篇文章的内容,进行一个简单回顾。本文首先介绍了什么是Maven,以及Maven的主要的功能,相信大家通过阅读文章也更清晰的了解到我们选择Maven的理由了。接下来我们又通过Maven进阶,更加详细的介绍了项目实战中我们经常使用到的分模块开发设计、私服配置以及多环境配置与应用。这些都是项目当中真实用到的,相信大家肯定会从中有所收获。


有了Maven之后,所有项目的构建命令都是简单一致的,这极大地避免了不必要的学习成本,而且有利于促进项目团队的标准化。综上所述,Maven作为一个构建工具,不仅能帮我们自动化构建,还能够抽象构建过程,提供构建任务实现;它可以跨平台,对外提供一致的操作接口,这一切足以使它成为优秀的、流行的构建工具。


本文作者


蜘蛛侠,来自缦图互联网中心后端团队。



--------END--------



也许你还想看

  | 走近java并发同步器AQS
  | 从volatile关键字到多核CPU的线程可见性
  | 你还在用MysqlDump同步数据么?


继续滑动看下一个
缦图coder
向上滑动看下一个