深入剖析 Maven 规则

     网上有很多maven配置和使用的内容,本文不重复讲解,只介绍maven的一些规则,在开发工作中不会被一些问题卡住。

     

一、什么是maven,为什么要用maven

      作为一门编译型语言,和其它编译语言相同,需要进行编译,测试,部署等必须流程,也就是通常说的软件生命周期。我们需要一个工具能够帮我们自动的完成这样一些没有意义的事情,把精力集中在程序开发和业务处理上。C和C++有make,Java对应的是Maven,Ant,Gradle等。Ant已经过时,Gradle比较新,是基于Groovy的一个工具,配置比maven简单,也兼容maven,但目前主流的还是相对成熟的maven。

      另外,Java大型工作,都会依赖一堆第三方类库或框架。比如数据库要依赖jdbc,使用了文件系统,就要引入该文件系统提供的Java API,还包含开源组织比如apache的commons-lang等一些封装好的类库,避免重复造轮子,同时也提高代码可读性。但是我们怎么获取?每个包都在一个自己的网站上,下载链接也不一定一样,而且本身也会依赖其它的东西。如果手工一个个从网站搜索下载,基本不用干活了。所以必须有一个可以自动处理依赖的工具。

       所以maven基本就是解决了两个问题,一个是项目构建,一个是处理依赖。

 

、maven规则

 1、坐标

       maven对于如何识别一个依赖,使用了坐标的概念,坐标一共5个标签,除去不常用的classfier,配置如下:

       

<groupId>org.apache</groupId>
<artifactId>commons-lang</artifactId>
<version>1.4.1</version>
<package>jar</package>

   

    groupId :通常是组织的定义,当然可以随便取,但是不要跟别人的重复。通用建议是,用组织或者公司的域名,反过来                          就可以。比如apache,就是org.apache。

    artifactId:一个组织下面可能会有很多个工程或者组件,标识这个工程的名称

    version: 这个工程的版本,对应Java有两种,比如1.1,或1.0-SNAPSHOT,用于稳定版或快照版。

    package:默认是jar,可以显式定义成别的,比如ear,war,pom,或安卓里的apk,处理pom外,实质都是zip包。

    定义了这三个标签,基本可以全球唯一定位一个工程。

 

2、依赖

     有两种,一个是denpency,一个是dependencyManagerment。

    

     2.1 dependency

     使用dependecy,配置只是把需要依赖的工程的坐标包含进去就可以了,dependencies标签里可定义多个dependency。

     

<dependencies>
  <dependency>
    <groupId>org.apache</groupId>
    <artifactId>commons-lang</artifactId>
    <version>1.4.1</version>
  </dependency>
</dependencies>
    类比于make是基于Make file,maven是基于pom文 件,pom文件是一个标准的xml。在${M2_HOME}/lib/maven-model-builder-3.0.4.jar 中,可以找到 / org/apache/maven/model/pom-4.0.0.xml这个文件,maven3项目都继承该配置。

 

    本质是maven有一个中央仓库,执行mvn命令的时候,在当前目录下找到pom文件,读取配置中的依赖坐标,然后去中央仓库查找,找到后放在本地仓库,也就是 .m2/repository中缓存起来。而maven的文件管理,就是把坐标中的点,替换成/,针对上面的例子,就是/org/apache/commons-lang/1.4.1/,从而在使用时能依赖该jar包。

 

      2.2 父pom

      对于多个工程,每个pom都可能依赖相同的包,这样可以创建一个父pom,将重复的dependency放到父pom里。在工程的pom中,一般在开头定义parent, 之后工程就会继承父pom的配置,类似于Java中的重复方法抽象。

       但是父pom,编译完以后,生成的还是pom,并没有实际的东西。配置如下:

       

<parent>
    <groupId>com.demo</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0</version>
</parent>
 

 

     2.3 传递性依赖

     对于maven,依赖是有传递性的。比如A 依赖 B,B依赖E , E依赖C(1.0版本),这样A 会默认的依赖 C (1.0版本)。然后A 又依赖 D, D 依赖 C (2.0版本),那么A到底是依赖1.0,还是依赖2.0版本的C?

     为解决传递性依赖,maven默认有两个规则:

     (1)路径最近者优先

      如上面的例子,有两个依赖路径, A -> B -> E -> C(1.0),A -> D -> C (2.0),后面的路径较短,选后面那个

      (2)第一声明者优先

       依赖路径相同时,比如A -> B -> C(1.0),A -> D -> C (2.0),这时路径是相同的,如果pom中B在D前面,就选B依赖的C(1.0).

 

      2.4 可选依赖

      有一种场景,比如 A -> B, B -> X,  B -> Y, 但是X和Y是相互冲突的,B可能是一个持久层隔离的工具包,包含了多种数据库比如Mysql, PgSql,在构建的时候,需要依赖两个工具包,但是运行时,只能使用一个。那么可以定义可选依赖。配置如下:

       

<dependencies>
  <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.5.0</version>
       <optional>true</optional>
  </dependency>
  <dependency>
       <groupId>postgresql</groupId>
       <artifactId>postgresql</artifactId>
       <version>9,4-701.jdbc3</version>
       <optional>true<optional>
  </dependency>
</dependencies>
    这样两个sql的配置只会对B有影响,而不是对依赖B的项目比如A有影响,但是A使用mysql时,必须显式依赖mysql-connector-java。

 

     2.5 依赖冲突

     有一条依赖关系链,比如 A -> B -> C (1.0-SNAPSHOT) , A -> C (1.1),但是并不想让B依赖 C,而是自己显式指定依赖C,那么可以在dependency中使用exclusion排除掉C的snapshot版本。配置如下:

     

<dependencies>
   <groupId>org.apache</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>1.0</version>
   <exclusions>
        <exclusion>
             <groupId>com.coderdream</groupId>
             <artifactId>log4j</artifactId>
        </exclusion>
   </exclusions>
</dependencies>
    这样将指定的依赖的传递性依赖排除,但是该做法必须要很明白,排除掉不会出错,否则在一些场景中会出现不可预料的错误,比如spring依赖的一些模块。

 

     2.6 dependencyManagement

     在父pom中,可以定义dependencyManagement,这样在子工程中,不必在指定版本号。

     但是需要注意的是,dependencyManagement本身只声明依赖,不会自动引入,引入是子工程定义的时候配置的。也就是说,使用了dependencyManagement以后,同样的配置会在两个地方出现,只是一个有version,一个没有version。

     这样看起来很不方便,但是有点类似C语言里的宏定义,或者pom中properties里的变量,能够将版本控制在一个配置中,不不必担心子模块依赖版本不同的情况。配置如下:

     

<!-- 父pom -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache</groupId>
            <artifactId>commons-lang</artifactId>
            <version>3.4.1</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- 子pom  -->
<dependency>
   <groupId>org.apache</groupId>
   <artifactId>commons-lang</artifactId>
</dependency>
    当然,这两个配置也可以在同一个文件中出现。只是没有必要

 

    2.7 依赖环

    比如 A -> B  -> C ,通常build都是正常的。但是如果有一次,将 C -> A ,这就形成了一个依赖环。需要说明的是,build时候,不会报错。但是C依赖A的那个包的版本,绝对不是最新的。在软件开发里,这样做显然也是不对的。

 

三、maven私服

      每次都从maven的中央镜像中下载,对带宽来说是一件很痛苦的事情。同样,在公司中,很多工程也是依赖的,被依赖的工程需要发布到一个远程仓库中,供其他工程下载。所以类似于远程仓库的概念,很多公司都内部搭建了一个maven镜像,缓存中央镜像的包,同时存储内部的包。常用的是maven官方的nexus。

      对于nexus, 除非CM同事,Java开发也并不需要了解太多,只要知道上面能不能搜索到自己想要的包就行。另外,介绍下仓库的url。nexus中,有几种仓库,如下:

      (1)central,代理maven中央仓库

       ( 2 ) releases , 稳定版宿主型仓库

       ( 3 ) 3rd Party, 用于部署无法从公共仓库获得的第三方发布版本

      ( 4 )  public repositories, 将上面策略为release的仓库,url都转换成统一的地址,也就是说,你下载的url是 /nexus/content/group/public/commons-lang.jar,但是在实际文件存储时,并没有public这个目录,这个只是虚拟的url。

四、Profile

    在实际工作中,通常会有开发环境,测试环境,生产环境等至少三套环境。用于保证生产环境的稳定。这样,就会导致数据库的配置,以及一些resource的配置完全不同,使用profile可以解决。配置如下:

     

<profiles>
   <profile>
      <id>dev</id>
      <properties>
           <db.driver>com.mysql.jdbc.Driver</db.driver>
           <db.url>jdbc:mysql//localhost:3306/demo</db.url>
           <db.username>dev</db.username>
           <db.password>dev-pwd</db.passwd>
       </properties>
   </profile>

   <profile>
      <id>online</id>
      <properties>
           <db.driver>com.mysql.jdbc.Driver</db.driver>
           <db.url>jdbc:mysql//localhost:3306/demo</db.url>
           <db.username>online</db.username>
           <db.password>online-pwd</db.passwd>
       </properties>
   </profile>
</profiles>

    构建时只需要指定参数-Pdev,就可以将对应的dev的配置文件等拷贝过去,而不是获取其他的配置文件。当然,如果不指定,某些文件可能会找不到,是会报错的。

     

 五、其它  

      其他还有一些小技巧,比如发生重复依赖冲突时,使用分析依赖的命令,mvn dependency:tree, 不想做单元测试时,可以指定 -Dmaven.test.skip=true ,还有maven的依赖范围,是compile,还是provider等等,在实际用上的时候,网上查找即可。

猜你喜欢

转载自linbeisaii.iteye.com/blog/2203476