• Maven学习笔记


    一、基础概念

    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

  • 相关阅读:
    实验四 主存空间的分配和回收模拟
    实验一
    实验3观后感
    实验三进程调度模拟程序
    实验2作业调度
    0909 学习操作系统
    实验四 主存空间的分配和回收模拟
    实验三 同学互评
    实验三 进程调度模拟程序
    实验二 作业调度模拟程序
  • 原文地址:https://www.cnblogs.com/kuotian/p/15058783.html
Copyright © 2020-2023  润新知