一、基础概念

1.1 Maven是什么

Maven是一个项目构建,依赖管理和项目管理工具。它提供了一套标准化的项目结构,一套标准化的构建流程(编译,测试,打包,发布…),一套依赖管理机制(对jar统一管理,自动去中央仓库下载相关依赖,并解决依赖的依赖问题)。

1.2 为什么使用Maven?

由于 Java 的生态非常丰富,无论你想实现什么功能,都能找到对应的工具类,这些工具类都是以 jar 包的形式出现的,例如 Spring,SpringMVC、MyBatis、数据库驱动,等等,都是以 jar 包的形式出现的,jar 包之间会有关联,在使用一个依赖之前,还需要确定这个依赖所依赖的其他依赖,所以,当项目比较大的时候,依赖管理会变得非常麻烦臃肿,这是 Maven 解决的第一个问题。

Maven 还可以处理多模块项目。简单的项目,单模块分包处理即可,如果项目比较复杂,要做成多模块项目,例如一个电商项目有订单模块、会员模块、商品模块、支付模块...,一般来说,多模块项目,每一个模块无法独立运行,要多个模块合在一起,项目才可以运行,这个时候,借助 Maven 工具,可以实现项目的一键打包。

二、Maven项目结构

2.1 项目结构

Maven的标准目录结构默认如下:

a-maven-project 项目名
├── pom.xml 项目描述文件
├── src
│        ├── main
│        │         ├── java 项目的Java源码
│        │         └── resources 项目的资源,例如property文件,spring.xml
│        └── test
│                    ├── java 项目的测试源码
│                    └── resources 测试用的资源
└── target
             └──classes 编译输出目录

 

2.2 项目描述文件pom.xml

POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。执行任务或目标时,Maven 会在当前目录中查找 POM。它读取 POM,获取所需的配置信息,然后执行目标。

POM 中可以指定以下配置:项目依赖,插件,执行目标,项目构建 profile,项目版本等。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- 模型版本 -->
    <modelVersion>4.0.0</modelVersion>
    <!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group -->
    <groupId>com.companyname.project-group</groupId>
    <!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 -->
    <artifactId>project</artifactId>
    <!-- 版本号 -->
    <version>1.0</version>
    <!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型 -->
      <packaging>jar</packaging>
    <name>用户更为友好的项目名称</name>
    ......
</project>

 

第一个标签是xml头,其指定了该xml文档的版本和编码方式。

第二个标签project,是工程的根标签。它声明了一些POM相关的命名空间及xsd元素。

<modelVersion>指定了当前的POM模型的版本,Maven3的模型版本只能是4.0.0。

<groupId> 是项目组的标识,定义了项目属于哪个组,它在一个组织或者项目中是唯一的。例如,谷歌公司的myapp项目组,就取名为 com.google.myapp。

<artifactId>是项目的标识,它通常是项目的名称。groupId 和 artifactId 一起定义了 Artifact 在仓库中的位置。

<version>是版本号。例如,0.0.1-SNAPSHOT,SNAPSHOT意为快照,说明该项目还处于开发中,是不稳定的。

<groupId> 、<artifactId>和<version>定义了一个项目基本的坐标,任何的jar、pom都是以基于这三个基本的坐标进行区分的。

<name>声明六一个对用户友好的项目名称,但不是必须的。

三、Maven依赖管理

依赖管理是Maven的核心功能。Maven提供了多模块项目的模块间的复杂依赖关系的管理问题,以及我们的项目所依赖的第三方jar包的下载问题。

例如,我们的项目依赖abc这个jar包,而abc又依赖xyz这个jar包。当我们声明了自己的项目需要abc,Maven会自动导入abc的jar包,再判断出abc需要xyz,又会自动导入xyz的jar包,这样,最终我们的项目会依赖abc和xyz两个jar包。当我们声明一个spring-boot-starter-web依赖时,Maven会自动解析并判断最终需要大概二三十个其他依赖。如果我们自己去手动管理这些依赖是非常费时费力的,而且出错的概率很大。

3.1 依赖的配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.companyname.project-group</groupId>
    <artifactId>project</artifactId>
    <version>1.0</version>
      <packaging>jar</packaging>
  
     <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。 -->
     <dependencies>
         <!--参见dependencies/dependency元素 -->
         <dependency>
             <groupId>项目组</groupId>
             <artifactId>项目</artifactId>
             <version>版本</version>
                <type>依赖类型</type>
                <scope>依赖范围</scope>
                <optional>依赖是否可选</optional>
                   <!—主要用于排除传递性依赖-->
                   <exclusions>
                       <exclusion>
                         <groupId></groupId>
                        <artifactId></artifactId>
                     </exclusion>
                             </exclusions>
         </dependency>
         ......
     </dependencies>

</project>

根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:

  • grounpId、artifactId和version:依赖的基本坐标,Maven根据坐标才能找到需要的依赖。

  • type:依赖的类型,对应项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar

  • scope:依赖的范围

  • optional:标记依赖是否可选

  • exclusions:用来排除传递性依赖

3.2 依赖范围

Maven有如下几种依赖范围:

  • compile(默认): 编译时需要用到当前依赖,该依赖参与项目的编译、运行、测试、打包。

  • test: 依赖只在编译测试代码和运行测试代码的时候需要,在编译主代码或者运行项目的使用时将无法使用此依赖,打包的时候也不会包含。例如Jnuit,它只有在编译测试代码及运行测试的时候才需要。

  • provided:依赖在编译时需要用到,但运行时无效。参与项目的编译、运行、测试,但是不参与打包。例如servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器以及提供,就不需要Maven重复地引入一遍。

  • runtime: 编译时不需要用到当前依赖,但运行时需要用到。该依赖不参与项目编译,参与项目的运行、测试、打包。例如JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

  • system:依赖范围和provided完全一致,但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能构成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量,如:

    •   
      <dependency>
          <groupId>javax.sql</groupId>
          <artifactId>jdbc-stdext</artifactId>
          <Version>2.0</Version>
          <scope>system</scope>
          <systemPath>${java.home}/lib/rt.jar</systemPath>
      </dependency>

       

3.3传递性依赖

假设项目A依赖于项目B,项目B依赖于项目C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖和第二直接依赖的范围决定了传递性依赖的范围,如下表所示,最左边一行表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递依赖范围。

 

compile

(第二直接依赖范围)

test

provided

runtime

compile

(第一直接依赖范围)

compile

-

-

runtime

test

test

-

-

test

provided

provided

-

provided

provided

runtime

runtime

-

-

runtime

有了传递性依赖机制,A在使用B的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

3.4 依赖调节

依赖调节的作用是当多个手动创建的版本同时出现时,决定哪个依赖版本将会被使用。

依赖调节的两大原则:

  1. 路径最近原则:例如项目有A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,所以根据路径最近原则,A->D->X(2.0)路径短,所以X(2.0)会被解析使用。

    • 若在A中直接引入对X(1.0)的依赖,则其路径更短,X(1.0)被解析使用。

  2. 第一声明者优先原则:例如项目有A有这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),项目A到Y(1.0)和Y(2.0)的路径一样长,路径最近原则不适用,根据第一声明者优先原则,先声明的被解析。

3.5 可选依赖

项目中A依赖B,B依赖于X和Y,如果所有这三个的范围都是compile的话,那么X和Y就是A的compile范围的传递性依赖。但是,如果想让X、Y不作为A的传递性依赖,就需要使用配置可选依赖。用<optional>true</optional>标识可选依赖,这样A如果想用X、Y就要直接显示的添加依赖了。

<project>  
    <modelVersion>4.0.0</modelVersion>  
    <groupId>com.companyname.project-group</groupId>
    <artifactId>project-b</artifactId>
    <version>1.0.0</version>
    
    <dependencies>  
        <dependency>  
            <groupId>com.companyname.project-group</groupId>
            <artifactId>project-x</artifactId>  
            <version>1.1.2</version>  
            <optional>true</optional>  
        </dependency>  
        <dependency>  
            <groupId>com.companyname.project-group</groupId>
            <artifactId>project-y</artifactId>  
            <version>1.1.1</version>  
            <optional>true</optional>  
        </dependency>  
    </dependencies>  
</project>  

3.6 排除依赖

有时候你引入的依赖中包含你不想要的依赖,这时候就要用到排除依赖了,例如spring-boot-starter-web自带了logback这个日志包,我想引入log4j2的,所以我先排除掉logback的依赖包,再引入想要的包就行了。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

排除依赖代码结构:

<exclusions>
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
</exclusions>

注意:声明exclustion的时候只需要groupId和artifactId,而不需要version元素,这是只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。

四、仓库

4.1 仓库的概念

在Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。得益于坐标机制,任何Maven项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。

实际的Maven项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(例如,编译项目的时候需要将依赖加入到classpath中),Maven会自动根据坐标找到仓库中的构件,并使用它们。

为了实现重用,项目构建完毕后可生成的构件也可以安装或者部署到仓库中,供其他项目使用。

4.2 仓库的分类

Maven仓库分为本地仓库和远程仓库,远程仓库又分为中央仓库,私服仓库等。

本地仓库

本地仓库是指把本地开发的构件“发布”在本地,这样其他项目可以通过本地仓库引用它。但是我们不推荐把自己的模块安装到Maven的本地仓库,因为每次修改某个模块的源码,都需要重新安装,非常容易出现版本不一致的情况。更好的方法是使用模块化编译,在编译的时候,告诉Maven几个模块之间存在依赖关系,需要一块编译,Maven就会自动按依赖顺序编译这些模块。

运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。

中央仓库

Maven 中央仓库是由 Maven 社区提供的仓库,由Apache 团队Apache 团队来维护。其中包含了绝大多数流行的开源Java构件,它们由第三方模块的开发者自己把编译好构建发布到Maven的中央仓库之中。

私有仓库

私有仓库是指公司内部如果不希望把源码和jar包放到公网上,那么可以搭建私有仓库。私有仓库总是在公司内部使用,它只需要在本地的~/.m2/settings.xml中配置好,使用方式和中央仓位没有任何区别。

4.3 Maven 依赖搜索顺序

当我们执行 Maven 构建命令时,Maven 开始按照以下顺序查找依赖的库:

  1. 在本地仓库中搜索,如果找不到,执行步骤 2,如果找到了则执行其他操作。

  2. 在中央仓库中搜索,如果找不到,并且有一个或多个远程仓库已经设置,则执行步骤 4,如果找到了则下载到本地仓库中以备将来引用。

  3. 如果远程仓库没有被设置,Maven 将简单的停滞处理并抛出错误(无法找到依赖的文件)。

  4. 在一个或多个远程仓库中搜索依赖的文件,如果找到则下载到本地仓库以备将来引用,否则 Maven 将停止处理并抛出错误(无法找到依赖的文件)。

五、Maven命令

5.1 Maven生命周期

5.1.1 default生命周期

Maven的生命周期由一系列阶段(phase)构成,以内置的生命周期default为例,它包含以下phase:

生命周期阶段phase

描述

validate(校验)

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

initialize(初始化)

初始化构建状态,比如设置属性值。

generate-sources(生成源代码)

生成包含在编译阶段中的任何源代码。

process-sources(处理源代码)

处理源代码,比如说,过滤任意值。

generate-resources(生成资源文件)

生成将会包含在项目包中的资源文件。

process-resources (处理资源文件)

复制和处理资源到目标目录,为打包阶段最好准备。

compile(编译)

编译项目的源代码。

process-classes(处理类文件)

处理编译生成的文件,比如说对Java class文件做字节码改善优化。

generate-test-sources(生成测试源代码)

生成包含在编译阶段中的任何测试源代码。

process-test-sources(处理测试源代码)

处理测试源代码,比如说,过滤任意值。

generate-test-resources(生成测试资源文件)

为测试创建资源文件。

process-test-resources(处理测试资源文件)

复制和处理测试资源到目标目录。

test-compile(编译测试源码)

编译测试源代码到测试目标目录.

process-test-classes(处理测试类文件)

处理测试源码编译生成的文件。

test(测试)

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

prepare-package(准备打包)

在实际打包之前,执行任何的必要的操作为打包做准备。

package(打包)

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

pre-integration-test(集成测试前)

在执行集成测试前进行必要的动作。比如说,搭建需要的环境。

integration-test(集成测试)

处理和部署项目到可以运行集成测试环境中。

post-integration-test(集成测试后)

在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。

verify (验证)

运行任意的检查来验证项目包有效且达到质量标准。

install(安装)

安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。

deploy(部署)

将最终的项目包复制到远程仓库中与其他开发者和项目共享。

如果我们运行mvn package,Maven就会执行default生命周期,它会从头开始一直运行到package这个phase为止。即validate,...,package。

如果我们运行mvn compile,Maven也会执行default生命周期,但这次它只会运行到compile,即以下几个phase。即validate,...,compile。

5.1.2 clean生命周期

Maven另一个常用的生命周期是clean,它会执行3个phase:

  • pre-clean:执行一些需要在clean之前完成的工作

  • clean:移除所有上一次构建生成的文件(注意这个clean不是lifecycle而是phase)

  • post-clean:执行一些需要在clean之后立刻完成的工作

我们使用mvn这个命令时,后面的参数是phase,Maven自动根据生命周期运行到指定的phase。

更复杂的例子是指定多个phase,例如,运行mvn clean package,Maven先执行clean生命周期并运行到clean这个phase,然后执行default生命周期并运行到package这个phase。即pre-clean,clean (注意这个clean是phase),validate,...,package。

5.1.3 site生命周期

Maven Site 插件一般用来创建新的报告文档、部署站点等。

  • pre-site:执行一些需要在生成站点文档之前完成的工作

  • site:生成项目的站点文档

  • post-site: 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备

  • site-deploy:将生成的站点文档部署到特定的服务器上

5.2 Maven命令

在实际开发过程中,经常使用的命令有:

  • mvn clean:清理所有生成的class和jar;

  • mvn clean compile:先清理,再执行到compile;

  • mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile;

  • mvn clean package:先清理,再执行到package。

  • ...

六、模块管理

在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法。对于Maven工程来说,原来是一个大项目:

single-project
├── pom.xml
└── src

现在可以分拆成3个模块:

mutiple-project
├── module-a
│            ├── pom.xml
│            └── src
├── module-b
│            ├── pom.xml
│            └── src
└── module-c
               ├── pom.xml
               └── src

6.1 继承

Maven可以有效地管理多个模块,我们只需要把每个模块当作一个独立的Maven项目,它们有各自独立的pom.xml。我们提取模块的共同部分作为parent。

 

multiple-project
├── pom.xml
├── parent
│          └── pom.xml
├── module-a
│          ├── pom.xml
│          └── src
├── module-b
│          ├── pom.xml
│          └── src
└── module-c
             ├── pom.xml
             └── src

注意parent的<packaging>是pom而不是jar,因为parent本身不含任何Java代码。编写parent的pom.xml只是为了在各个模块中减少重复的配置。

 

module-a 继承了 parent 。

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>module-a</artifactId>
</project>

6.2 聚合

在编译的时候,需要在根目录创建一个pom.xml统一编译。multiple-project 聚合了三个工程。

<?xml version="1.0" encoding="UTF-8"?>
<project >
    <groupId>org.example</groupId>
    <artifactId>maven-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>parent</module>
        <module>maven-a</module>
        <module>maven-b</module>
        <module>maven-c</module>
    </modules>
</project>

这样,在根目录执行mvn clean package时,Maven根据根目录的pom.xml找到包括parent在内的共4个<module>,一次性全部编译。

 

小结:

  • 继承用于消除冗余配置,比如一些配置打包配置,配置的变量,项目依赖和插件依赖版本管理。

  • 聚合用于快速构建项目。聚合之前打包 a,b,c 需要分别运行 mvn package。聚合之后,我咱们只需要在 multiple-project 下运行 mvn package。

参考资料

https://www.runoob.com/maven/maven-tutorial.html

https://www.liaoxuefeng.com/wiki/1252599548343744/1255945359327200

http://tengj.top/2018/01/01/maven/

https://juejin.cn/post/6844904021392654350

https://juejin.cn/post/6844904182487449614

posted on 2021-07-25 19:48  kuotian  阅读(78)  评论(0编辑  收藏  举报