zoukankan      html  css  js  c++  java
  • Maven知识点整理

    Maven不仅是依赖管理工具,准确来说是一个项目管理工具,贯穿了整个项目生命周期,编译,测试,打包,发布...
    依赖是使用Maven坐标来定位的,而Maven坐标 主要 由GAV(groupId, artifactId, version)构成。
    Maven思想:约定大于配置。

    依赖归类

    使用<properties>来归类管理。
    示例:
    <dependencies>
      <dependency>
        <groupId>org.spring.framework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.spring.framework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.spring.framework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <dependency>
        <groupId>org.spring.framework</groupId>
        <artifactId>spring-mock</artifactId>
        <version>${spring.version}</version>
      </dependency>
    </dependencies>
    
    <properties>
      <spring.version>2.5</spring.version>
    </properties>
    

     多模块 & 继承

    在Maven的多模块工程中,都存在一个pom类型的工程作为根模块,该工程只包含一个pom.xml文件,在该文件中以模块(module)的形式声明它所包含的子模块,即多模块工程。

    在子模块的pom.xml文件中,又以parent的形式声明其所属的父模块,即继承。

    多模块的好处是你只需在根模块中执行Maven命令,Maven会分别在各个子模块中执行该命令,执行顺序通过Maven的Reactor机制决定。

    1)如果保留webapp和core中对maven-multi-module的父关系声明,即保留 “<parent>...  </parent>”,而删除maven-multi-module中的子模块声明,即“<modules>...<modules>”,会发生什么情况?此时,整个工程已经不是一个多模块工程,而只是具有父子关系的多个工程集合。如果我们在maven-multi-module目录下执行“mvn clean install”,Maven只会在maven-multi-module本身上执行该命令,继而只会将maven-multi-module安装到本地Repository中,而不会在webapp和core模块上执行该命令,因为Maven根本就不知道这两个子模块的存在。另外,如果我们在webapp目录下执行相同的命令,由于由子到父的关系还存在,Maven会在本地的Repository中找到maven-multi-module的pom.xml文件和对core的依赖(当然前提是他们存在于本地的Repository中),然后顺利执行该命令。

    (2)如果保留maven-multi-module中的子模块声明,而删除webapp和core中对maven-multi-module的父关系声明,又会出现什么情况呢?此时整个工程只是一个多模块工程,而没有父子关系。Maven会正确处理模块之间的依赖关系,即在webapp模块上执行Maven命令之前,会先在core模块上执行该命令,但是由于core和webapp模块不再继承自maven-multi-module,对于每一个依赖,他们都需要自己声明,比如我们需要分别在webapp和core的pom.xml文件中声明对Junit依赖。

    依赖范围(scope)

    引入依赖范围的原因示例:

    1 例如Junit,将Junit的jar包打包到发布包是没有必要的。

    2 例如servlet-api,为了编译通过,我们引入servlet-api jar包;但因为web容器也会提供servlet-api,如果将servlet-api打包至WAR包,就会造成依赖冲突。

    示例:

    <dependency>  
      <groupId>junit</groupId>  
      <artifactId>junit</artifactId>  
      <version>4.4</version>  
      <scope>test</test>  
    </dependency>
    
    <dependency>  
      <groupId>javax.servlet</groupId>  
      <artifactId>servlet-api</artifactId>  
      <version>3.1</version>  
      <scope>provided</scope>  
    </dependency>  
    

     scope详解:

    1 complie

    默认值。参与当前项目的编译,后续的测试,运行,是一个比较强的依赖。

    2 test

    参与包括测试代码的编译,执行。比较典型的如junit。

    3 runtime

    被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。
    与compile相比,跳过编译而已.
    oracle jdbc驱动架包就是一个很好的例子,一般scope为runntime。另外runntime的依赖通常和optional搭配使用,optional为true。我可以用A实现,也可以用B实现。

    4 provided

    打包的时候不包进去,别的设施(如Web Container)会提供。
    事实上该依赖理论上可以参与编译,测试,运行等周期。
    相当于compile,但是在打包阶段做了exclude的动作。

    5 system

    从参与度来说,也provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用。

    依赖范围及作用
    依赖范围(scope) 主源码classpath可用 测试源码classpath可用 会被打包
    compile 缺省值 TRUE TRUE TRUE
    test FALSE TRUE FALSE
    runtime FALSE TRUE TRUE
    provided TRUE(外界提供) TRUE FALSE

    scope的依赖传递

    A–>B–>C。当前项目为A,A依赖于B,B依赖于C。知道B在A项目中的scope,那么怎么知道C在A中的scope呢?答案是:
    当C是test或者provided时,C直接被丢弃,A不依赖C;
    否则A依赖C,C的scope继承于B的scope。

    下面是一张nexus画的图。 

    分类器(classifer)

    GAV是Maven坐标最基本最重要的组成部分,但GAV不是全部。

    还有一个元素叫做分类器(classifier),90%的情况你不会用到它,但有些时候,分类器非常不可或缺。
    举个简单的例子,当我们需要依赖TestNG的时候,简单的声明GAV会出错,因为TestNG强制需要你提供分类器,以区别jdk14和jdk15,我们需要这样声明对TestNG的依赖:

    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.7</version>
      <classifier>jdk15</classifier>
    </dependency>
    

    你会注意到maven下载了一个名为testng-5.7-jdk15.jar的文件。

    其命名模式实际上是<artifactId>-<version>-<classifier>.<packaging>。

    理解了这个模式以后,你就会发现很多文件其实都是默认构件的分类器扩展,如 myapp-1.0-test.jar, myapp-1.0-sources.jar。

    分类器还有一个非常有用的用途是:我们可以用它来声明对test构件的依赖,比如,我们在一个核心模块的src/test/java中声明了一些基础类,然后我们发现这些测试基础类对于很多其它模块的测试类都有用。没有分类器,我们是没有办法去依赖src/test/java中的内容的,因为这些内容不会被打包到主构件中,它们单独的被打包成一个模式为<artifactId>-<version>-test.jar的文件。

    我们可以使用分类器来依赖这样的test构件:理解了分类器,那么可供依赖的资源就变得更加丰富。

    <dependency>
      <groupId>org.myorg.myapp</groupId>
      <artifactId>core</artifactId>
      <version>${project.version}</version>
      <classifier>test</classifier>
    </dependency>
    

     依赖管理(dependencyManagement)

    实际的项目中,你会有一大把的Maven模块,而且你往往发现这些模块有很多依赖是完全项目的,A模块有个对spring的依赖,B模块也有,它们的依赖配置一模一样,同样的groupId, artifactId, version,或者还有exclusions, classifer。

    细心的分会发现这是一种重复,重复就意味着潜在的问题,Maven提供的dependencyManagement就是用来消除这种重复的。

    正确的做法是:

    1. 在父模块中使用dependencyManagement配置依赖

    2. 在子模块中使用dependencies添加依赖

    dependencyManagement实际上不会真正引入任何依赖,dependencies才会。但是,当父模块中配置了某个依赖之后,子模块只需使用简单groupId和artifactId就能自动继承相应的父模块依赖配置。

    依赖配置越复杂,依赖管理所起到的作用就越大,它不仅能够帮助你简化配置,它还能够帮你巩固依赖配置,也就是说,在整个项目中,对于某个构件(如mysql)的依赖配置只有一种,这样就能避免引入不同版本的依赖,避免依赖冲突。

    示例:

    父模块:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.sonatype.mavenbook</groupId>
      <artifactId>a-parent</artifactId>
      <version>1.0.0</version>
      ...
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.2</version>
          </dependency>
          ...
        <dependencies>
      </dependencyManagement>
    

    子模块:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <parent>
        <groupId>org.sonatype.mavenbook</groupId>
        <artifactId>a-parent</artifactId>
        <version>1.0.0</version>
      </parent>
      <artifactId>project-a</artifactId>
      ...
      <dependencies>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
        </dependency>
      </dependencies>
    </project>
    

    传递依赖 & 依赖冲突

    依赖冲突时,Maven采用“最近获胜策略(nearest wins strategy)”。

    如果一个项目最终依赖于相同artifact的多个版本,在依赖树中离项目最近的那个版本将被使用。

    示例:

    github源代码:https://github.com/davenkin/maven-dependency-conflict-demo

    resolve-web项目的依赖关系如下:

    根据Maven的transitive依赖机制,resolve-web将同时依赖于project-common的1.0和2.0版本,这就造成了依赖冲突。而根据最近获胜策略,Maven将选择project-common的1.0版本作为最终的依赖。

     在resolve-web中执行"mvn dependency:tree -Dverbose"可以看到resolve-web的依赖关系:

    [INFO] resolve-web:resolve-web:war:1.0-SNAPSHOT
    
    [INFO] +- junit:junit:jar:3.8.1:test
    
    [INFO] +- project-B:project-B:jar:1.0:compile
    
    [INFO] |  - project-C:project-C:jar:1.0:compile
    
    [INFO] |     - (project-common:project-commmon:jar:2.0:compile - omitted for conflict with 1.0)
    
    [INFO] +- project-A:project-A:jar:1.0:compile
    
    [INFO] |  - project-common:project-commmon:jar:1.0:compile
    
    [INFO] - javax.servlet:servlet-api:jar:2.4:provided
    

    由上可知,project-common:project-commmon:jar:2.0被忽略掉了。此时在resolve-web的war包中将只包含project-common的1.0版本,于是问题来了。由于project-common的1.0版本中不包含sayGoodBye()方法,而该方法正是project-C所需要的,所以运行时将出现“NoSuchMethodError”。

    对于这种有依赖冲突所导致的问题,我们有两种解决方法。

    方法1:显式加入对project-common 2.0版本的依赖。先前的2.0版本不是离resolve-web远了点吗,那我们就直接将它作为resolve-web的依赖,这不就比1.0版本离resolve-web还近吗?在resove-web的pom.xml文件中直接加上对project-common 2.0 的依赖:

    <dependency>       
       <groupId>project-common</groupId>      
       <artifactId>project-commmon</artifactId>  
       <version>2.0</version>   
    </dependency>  
    

    方法2:resolve-web对project-A的dependency声明中,将project-common排除掉。在resolve-web的pom.xml文件中修改对project-A的dependency声明:

    <dependency>  
              <groupId>project-A</groupId>  
              <artifactId>project-A</artifactId>  
              <version>1.0</version>  
              <exclusions>  
                  <exclusion>  
                      <groupId>project-common</groupId>  
                      <artifactId>project-commmon</artifactId>  
                  </exclusion>  
              </exclusions>  
    </dependency>
    

    方法3:另外,我们还可以在project-A中将对project-common的依赖声明为optional,optional即表示非transitive,此时当在resolve-web中引用project-A时,Maven并不会将project-common作为transitive依赖自动加入,除非有别的项目(比如project-B)声明了对project-common的transitive依赖或者我们在resolve-web中显式声明对project-common的依赖(方法一)。

     Profile

    Maven的Profile用于在不同的环境下应用不同的配置。一套配置即称为一个Profile。

     Maven提供了Activation机制来激活某个Profile,它既允许自动激活(即在某些条件满足时自动使某个Profile生效),也可以手动激活。

    Profile可配置“<activeByDefault>true</activeByDefault>”,表明该Profile默认即是生效的。

    <profiles>
    
           <profile>
    
               <id>apple</id>
    
               <activation>
    
                   <activeByDefault>true</activeByDefault>
    
               </activation>
    
               <properties>
    
                   <fruit>APPLE</fruit>
    
               </properties>
    
           </profile>
    
           <profile>
    
               <id>banana</id>
    
               <properties>
    
                   <fruit>BANANA</fruit>
    
               </properties>
    
           </profile>
    
       </profiles>
    

    为了打印出fruit这个属性,我们再向pom.xml中添加一个maven-antrun-plugin插件,我们可以通过该插件的echo任务来打印属性信息。我们将该打印配置在Maven的initialize阶段(任何阶段都可以):

    <plugin>
    
                   <artifactId>maven-antrun-plugin</artifactId>
    
                   <executions>
    
                       <execution>
    
                           <phase>initialize</phase>
    
                           <goals>
    
                               <goal>run</goal>
    
                           </goals>
    
                           <configuration>
    
                               <tasks>
    
                                   <echo>Fruit:${fruit}</echo>
    
                               </tasks>
    
                           </configuration>
    
                       </execution>
    
                   </executions>
    
               </plugin>
    

    配置完成之后,执行:“mvn initialize”,由于id为apple的Profile默认生效,此时将在终端输出“APPLE”字样:

    ......
    
    [INFO] Executing tasks
    
        [echo] Fruit:APPLE
    
    [INFO] Executed tasks
    
    ......
    

    如果要使用id为banana的Profile,我们可以显式地指定使用该Profile:"mvn initialize -Pbanana"

    私服(Repository)

     Maven提高篇系列之(三)——使用自己的Repository(Nexus)

    Plugin

    Maven就其本身来说只是提供一个执行环境,它并不知道需要在项目上完成什么操作,真正操作项目的是插件(plugin)。

    比如编译Java有Compiler插件,打包有Jar插件等。

    所以要让Maven完成各种各样的任务,我们需要配置不同的插件,甚至自己编写插件。 

    参考资料:

    Maven提高篇系列之(一)——多模块 vs 继承

    Maven提高篇系列之(三)——使用自己的Repository(Nexus)

    Maven提高篇系列之(四)——使用Profile

    Maven提高篇系列之(五)——处理依赖冲突

    Maven提高篇系列之(六)——编写自己的Plugin

     Maven依赖中的scope详解

    Maven最佳实践:管理依赖

  • 相关阅读:
    【从0安装】安装pycharm
    【从0安装】安装appium
    【从0安装】安装android sdk
    【从0安装】安装java jdk
    【从0安装】安装nodejs
    【技术解析】如何用Docker实现SequoiaDB集群的快速部署
    巨杉数据库助力民生银行、广发银行前台智慧化业务
    巨杉数据库入选Gartner数据库报告,中国首家入选厂商
    【操作教程】利用YCSB测试巨杉数据库性能
    巨杉数据库首批入选广州数字金融协会,引领大湾区数字金融创新
  • 原文地址:https://www.cnblogs.com/ken-jl/p/8610775.html
Copyright © 2011-2022 走看看