首页 > 技术文章 > Maven初步

zyzdisciple 2017-12-07 16:39 原文

Maven初入

maven 是一个项目管理工具, 它包含了一个 项目对象模型(Project Object Model POM), 一组标准集合, 一个项目生命周期(Project Lifecycle), 一个依赖管理系统(Dependency Management System), 和用来运行 定义在 生命周期阶段中插件目标的逻辑.

Maven的核心只做一些基础的事情, 解析XML文档, 管理生命周期, 插件, 仅此而已; Maven的主要职责是委派给各种各样的插件, 如编译源码, 打包二进制代码, 发布站点 和 其他构建任务, 而插件从 maven仓库获得.

文档参考自: Maven权威指南

Maven初步

安装自不必赘述, Maven的相关配置在 ~/.m2/settings.xml,

~/.m2/repository 是本地仓库目录.

maven核心概念

  1. 插件和目标

    就像在前面所提到过的, Maven的核心所做的工作很少, 大部分工作都会叫给插件来做, 一个插件 是一个或多个目标的集合, 插件的直观表现就是一个或多个jar包, 目标指的就是 某一个具体的方法, 可以配置相应的参数, 同时需要给定一部分必须参数;

    简写: pluginId:goalId

  2. 生命周期(Lifecycle)

    生命周期是包含在一个项目中一系列有序的阶段, 在maven中配有默认的生命周期, 以验证项目的基本完整性开始, 以把项目发布成产品结束.

    其中, 插件目标是可以附着在生命周期上的, 会随着生命周期的阶段 一步步执行, 当 maven执行一个阶段的时候, 他首先会有序执行前面的所有阶段, 并执行绑定在阶段上的默认目标, 到命令指定的那个阶段为止;

    recourses: recourses; Recourses插件的 recourses目标绑定在了 recourses阶段, 这个目标复制 src/main/recourses 下的所有资源 和 其他任何配置的资源目录, 到输出目录;

    compiler: compile 绑定到了 compile阶段,编译 src/main/java 下的所有源代码 和 其他任何配置的资源目录, 到输出目录.

    recourses: testRecourses Resources插件的 testRecourses目标绑定到了 test-recourses阶段, 对应 src/test/resources

    compiler: testCompile 对应 src/test/java

    surefire: test surefire插件的目标test 绑定到 test阶段, 这个目标运行所有的测试, 并创建那些 捕捉 详细测试结果的文件, 默认情况下, 如果有测试失败, 这个目标会终止

    jar:jar 绑定到 package阶段, 把输出目录打包成 jar文件.

  3. Maven 坐标

    POM为项目命名, 提供了项目的一组唯一标识符(坐标), 并通过 依赖(dependencies), 父(parents) 和 先决条件(prerequisite) 来定义和其他项目的关系.

    Maven定义了一组坐标, 他们可以用来标识一个项目, 一个依赖, 或者MavenPom里一个插件.

    GroupId: 团体, 公司, 组织等其他, 就是java中的 com.company.project

    artifactId: 项目标识

    version: 版本号, 一般会通过加上 "SNAPSHOT"标记, 标识正在开发中.

    packing(非必须): 默认为 jar, 项目打包后输出, war 表示web 项目.

    同时需要注意的是: 在 artifactId中最好不要使用 "."

  4. Maven仓库

    路径为, 相对于仓库根目录:

    /<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packing>

  5. Maven依赖

    对于依赖, 目前只需要知道依赖具有传递性,即可.

Maven注意事项

  1. 优化, 降低依赖重复

    1. 上移共同的依赖至 dependencyManagement

      如果多于一个项目依赖于一个特定的依赖, 就可以在 dependencyManagement中列出这个依赖, 父POM包含一个版本和一组排除配置, 在子POM中需要使用 groupId和artifactId引用这个依赖, 如果依赖已经在 dependencyManagement中列出, 子项目可以额忽略版本和排除配置;

       <properties>
           <hibernate.annotation.version>3.3.0</hibernate.annotation.version>
       </properties>
      

      通过这种方式将版本信息定义为 常量, 通过 ${ }引用变量

       <dependency>
           <groupId>org.hibernate</groupId>
           <artifactId>hibernate-annotation</artifactId>
           <version>${hibernate.hibernate.annotation.version}</version>
       </dependency>
      
    2. 为兄弟项目使用内置的 version 和 groupId

    3. 使用相同的 groupId 和 ${project.version}.

    4. 上移共同的 plugin 至 pluginManagement

    5. 显示的声明依赖, 不要通过依赖传递性来引用 jar包

    6. 使用 Maven Denpendency插件优化:

      通过 mvn dependency:analyze 对项目进行分析, 但增删依赖判断的先决条件是单元测试;

      在 analyze中, 生命周期依次为:
      recourses, compile, testRecourses, testCompile, analyze;

      会将执行中的错误, 未声明的 jar, 声明未使用的jar 种种体现出来.

      而使用 mvn dependency:tree , 会列出项目中所有的直接和传递性依赖.

    7. 几个帮助插件

      mvn help:active-profiles 列出当前构建中活动的 Profile

      mvn help:effective-pom 显示当前构建的实际 pom

      mvn help:effective-settings 打印实际的setting, 包含全局和用户级别的

      mvn help:describe 描述插件的属性, 无需在项目目录下运行, 但必须提供插件的 groupId 和 artifactId

      如: mvn help:describe -Dplugin=complier [Dmojo=compile] [-Dfull]

      第二个参数表示插件中的某一个目标, 第三个参数为目标的所有信息, 同时在:

      "-D<name>=<value>" 这种方式设定 调用 mvn 插件目标 ,传入目标中的参数中的 值. 需要注意的是, 这并非是 Maven的语法, 其实是 java 用来设置系统属性的方式.

Maven Pom

所有的 Maven POM 都继承自超级POM, 超级POM所处目录为: {Maven HOME}/lib/maven-model-builder-{Maven Version}.jar 内 org.apache.maven.model 下的 POM-4.0.0.xml.

文件主要定义了:

  1. 单独的远程仓库, https://repo.maven.apache.org/maven2

     <repositories>
     <repository>
     <id>central</id>
     <name>Central Repository</name>
     <url>https://repo.maven.apache.org/maven2</url>
     <layout>default</layout>
     <snapshots>
         <enabled>false</enabled>
     </snapshots>
     </repository>
     </repositories>
    
  2. 为核心插件提供了默认的版本

我们自己建立的 POM最顶层继承自 超级 POM, 其次是所有的 父级POM, 自上向下 会一层层 覆盖之前的默认配置, 通过 mvn help:effective 可以查看当前项目的有效pom.

下面就开始一步步看POM.xml文件中的配置:

  1. version

     <major version>.<minor version>.<incremental version>-<qualifier>
    
     1.3.5-beta-01
    

    其中1为主版本号, 3为次版本号, 5为增量版本号, beta-01为限定版本号(alpha 和 beta). 如果版本号符合规则要求, 则按照版本号来比较, 否则按照字符串规则来进行比较. 比如 a10 是 排在 a2前面的, 因此 a10属于旧版本, 但在我们的定义中, 却应该是相反的.

    版本界限配置方式:

    (loVersion, hiVersion) 不包含
    [loVersion, hiVersion] 包含

     <version>[3.8, 4.1.2]</version>
    

    当不指定上界/下界时, 则为以上/以下

    SNAPSHOT

    如果你在版本中使用了 字符串 SNAPSHOT, 表示项目正处于 活动的 开发状态, Maven会在安装 或部署的时候将 符号展开为 一个时间和日期,转换为 UTC.(也就是快照版本)

    如在创建的时候, 默认为 0.1-SNAPSHOT, 而后将项目部署到了 Maven仓库, Maven会将版本展开为 0.1-YYYYMMDD-HH24MISS-1, 这里的时间 为UTC时间.

    如果项目依赖的一个组件正处于开发过程中, 依赖它的 SNAPSHOT版本, 则在运行构建的时候, 会自动从仓库下载最新的 SNAPSHOT, 同时, 要依赖 SNAPSHOT版本, 用户必须在 POM中使用 repository 和 pluginRepository 开启下载 SNAPSHOT功能.

     <!--选择对应的 坐标, 将 enabled 属性设置为 true 即可-->
     <repositories>
         <repository>
         <id>central</id>
         <name>Central Repository</name>
         <url>https://repo.maven.apache.org/maven2</url>
         <snapshots>
             <enabled>true</enabled>
         </snapshots>
         </repository>
     </repositories>
    
     <pluginRepositories>
         <pluginRepository>
         <id>central</id>
         <name>Central Repository</name>
         <url>https://repo.maven.apache.org/maven2</url>
         <snapshots>
             <enabled>true</enabled>
         </snapshots>
         </pluginRepository>
     </pluginRepositories>
    

    而发布到 非 snapshot的 Maven仓库的构建不能依赖于 SNAPSHOT版本, 仅用于开发过程.

    LATEST 和 RELEASE

    当依赖于一个插件 或 依赖, 可以使用特殊值:

    LATEST: 最新发布的版本 或 快照版.

    RELEASE: 仓库中 最后一个非快照版本.

    在项目发布的时候, 尽量不要使用 这三个关键字, 仅在开发时使用

  2. 变量引用

    在Maven中有三个内置隐士变量:

    env: 表示 environment, 系统的环境变量, 如 ${env.PATH}, 需要注意的是需要大写, 如 ${env.JAVA_HOME}

    settings: 这个指的是 settings.xml中的属性, 但一直引用不成功.

    project: 引用当前 project中的属性 如 ${project.artifactId}.

    ${basedir}, 可以访问当前项目根目录

    Java系统属性

    凡是可以被 java.lang.System 中的 getProperties()方法访问的属性

    可以在Java中通过 System.getProperties().list(System.out); 在Maven中通过 ${} 直接访问, 如 ${java.runtime.name}

    自定义属性:

     <properties>
         <attribute>value</attribute>
     </properties>
    

    访问方式即: ${attribute.value}

  3. 依赖范围

     <version>1.0</version>
     <scope>test</scope>
    

    compile: 编译范围, 默认范围, 在所有的classpath都可以使用, 也会被打包

    provided: 已提供范围, 只有当 JDK/容器已经提供该依赖之后才使用, 如在 开发中 需要 Servlet API编译一个 Servlet, 但在打包发布之后这部分应该由 容器/服务器本身来提供. 仅在编译时可用. 不可传递, 不会打包.

    runtime: 运行时范围, 在编译时不需要, 在运行时需要.

    test: 测试范围, 仅在测试时需要, 测试运行, 测试编译.

    system: 系统范围, 必须提供 systemPath, 即本地jar文件的路径.

    可选依赖:

     <version>1.0</version>
     <optional>true</optional>
    

    通过这种方式生命的依赖, 不会被传递. 需要在子项目中再度生命

  4. 依赖的传递

    test范围不会被传递, provided范围 仅在 provided中被传递, runtime 和 compile 在 四种范围都会被传递.

    e.g. : A 包含对 B的测试范围依赖, B 包含对 C的编译时依赖, 则 C为A的测试范围依赖

     A:
     <dependency>
         <groupId>B</groupId>
         <artifactId>B</artifactId>
         <version>1.0</version>
         <scope>test</scope>
     </dependency>
     B:
     <dependency>
         <groupId>C</groupId>
         <artifactId>C</artifactId>
         <version>1.0</version>
     </dependency>
    

    e.g. : A包含对 B的测试范围依赖, B包含对C的测试范围依赖, 则 A与C无关.

     A:
     <dependency>
         <groupId>B</groupId>
         <artifactId>B</artifactId>
         <version>1.0</version>
         <scope>test</scope>
     </dependency>
     B:
     <dependency>
         <groupId>C</groupId>
         <artifactId>C</artifactId>
         <version>1.0</version>
         <scope>test</scope>
     </dependency>
    

    依赖追踪:

    在 Maven中, 如果多个项目依赖于同一个 project, 则Maven会找到 所有依赖中版本最新的 依赖, 作为最终的选择, 所以可以排除对应的依赖, 且同时 可以 更换自己想要的版本.

    e.g.:

     <dependency>
         <groupId>B</groupId>
         <artifactId>B</artifactId>
         <version>1.0</version>
         <exclusions>
             <exclusion>
                 <groupId>C</groupId>
                 <artifactId>C</artifactId>
             </exclusion>
         </exclusions>
     </dependency>
    
  5. 依赖管理

    这在之前 优化 已经提到, 采取在父级项目中定义 dependencyManagement, pluginManagement, 在子级中沿用父级的版本, 仅需要列出所选取的 依赖坐标, 而无需再度定义版本(如果定义子级版本, 父级就会被忽略);

    多模块项目:

     <modules>
         <module>project-a</module>
         <module>project-b</module>
     </modules>
    

    多模块项目的打包总是一个 POM 而非构建, 其中各个模块可以为 POM 或者 jar

    项目继承:

     <parent>
         <groupId>C</groupId>
         <artifactId>C</artifactId>
         <version>1.0-SNAPSHOT</version>
     </parent>
    

    可以被继承的项目:

    • 定义符: groupId 和 artifactId 必须有一个被重写, 不能有完全相同的坐标.
    • 依赖
    • 开发者和贡献者
    • 插件列表
    • 报告列表
    • 插件执行
    • 插件配置

    继承中, 当 父级 POM 在 父目录../pom.xml 或者 在 本地仓库目录时 可用, 否则的话需要指明 父级POM的相对位置.

      <parent>
         <groupId>C</groupId>
         <artifactId>C</artifactId>
         <version>1.0-SNAPSHOT</version>
         <relativePath>../a-parent/pom.xml</relativePath>
     </parent>
    

暂时告一段落.

推荐阅读