Maven dependency and repository (part a)

我们看一个最常见到的例子:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
 
该例子表示你的project有一个junit dependency。你可能会问:这个dependency从哪里来?junit jar在哪里?
 
A dependency是对repository里的某个artifact的引用。POM定义了一个dependency,执行的时候就会根据dependencygroupId, artifactId and version,来search it from repository你根本不需要在pom.xml里指明该dependancy来自哪个repositorymaven会先查找local repository,如果local repository存在该dependancy引用的artifact,就使用它,如果不存在,则搜索所有你设置的remote repository,如果找到它,就把它downloadlocal repository缺省状态下,通常都是从central maven repository(http://www.ibiblio.org/maven2)来download artifact。如果你的pom.xml定义了多个remote repository,那么就按顺序依次试图从remote repository下载.
 
例如上面的例子,maven会先check local repository看是否有想要的junit artifact,如果没有,则从remote repository download到local repository。这时,local repository里artifact目录结构包含:
 Maven dependency and repository (part a)
 
下图是在repository里所有artifact通用的目录结构:
 Maven dependency and repository (part a)
 
groupId是fully qualified domain name,例如为x.y.z,则结构为:
 Maven dependency and repository (part a)
 
maven是如何根据定义的dependancy来从repository里查找呢?例如上例,就会根据groupId “junit”, artifactId “junit”, and version 3.8.1”,在repository里查找~/.m2/repository/junit/junit/3.8.1/junit-3.8.1.jar
 
 
dependancy功能是maven最强大的功能和最显著的优势。以前maven没出现之前,通常的project都会把要用到的jar files都放在project subdirectory下,或开发一个web app,你要把10多个jar添加到classpath,并把它们放到lib目录下。这样如果你开发100个类似的web app,你的每一个web app开发目录下面都会包含有这些jar files。这样如果某个jar的版本出现更新,就会要更新100project里的jar。而使用maven,就会大大减轻你的工作量。
 
例如,你有100个web app都使用了spring 1.2.6 framework,如果你使用maven,你不需要存储所有的spring jars到各个project里,你只需要在POM.XML里定义一个spring dependancy,那么如果升级到spring 2.0,只需要把pom.xml里dependancy的<version>修改为2.0即可。
 
 
Dependency机制介绍
 
Dependency机制的三个知识点
  • Transitive Dependencies
  • Dependency Scope
  • Dependency Management
Transitive Dependencies (可传递的依赖)
这是maven 2.0的新特征。它使你不需要指定dependency的位置而自动定位。而且可传递的依赖就是说依赖能够自动继承和传递,例如project A依赖project Bartifact,而project B则依赖project Cartifact,那么在build project A时,就会使用到所有project、子project的依赖。
 
一个project的依赖的总个数没有限制,但是如果出现死循环依赖则会出现问题。
 
由于依赖可以传递,因此有可能一个project要依赖的东东会很多,因此可以通过下列几种方式来限制要包括的dependency:
  • Dependency mediation意思是强烈建议显式设置你使用的dependency的版本号,因为dependency可能会有多种版本。目前Maven 2.0支持"nearest definition"(见下面的解释)。注意:如果在dependency tree的同一个depth里定义了同一个dependency2个版本,那么使用先定义的那个版本
    • "nearest definition" 表示在dependencies tree里最靠近你的project的版本。例如,如果project A的依赖性为:A -> B -> C -> D 2.0A -> E -> D 1.0, 那么D1.0版本将会被使用。因为从A通过E到达D的路径是最短的。如果本例你硬是想要使用D 2.0,那么你可以在A里定义一个Ddependency
 
  • Dependency management表示maven允许你在你的pom.xml里设置你要使用的depedency的版本号,即使这个depedency你可能不知道是从哪里来的可传递依赖,也不知道该依赖定义的版本是什么,你都可以根据“nearest definition”法则来在你的pom.xml里设置版本号。例如上面的例子,你可以直接在A里设置对D的依赖(设置版本号),即使A并不直接使用D
 
  • Dependency scope尽量为要包含的dependencies设置要用到它的scope下面会详细解释
 
 
Dependency Scope
Dependency scope是设置什么stage要使用它,用来限制依赖的传递。
 
总共有5种Scopes:
  • compile这是缺省scope,表明是所有任务所需的资源。“Compile dependencies are available in all classpaths.
  • provided表示该dependencyJDK部分或应用服务器的classpath里已经自带的资源,例如EJBdependency jars,只需要在compile时使用,在例如打包时就不应该把它打包进jar, war or ear里,因为JDK or APP SERVER里本身就有。
  • runtime - this scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.
  • test表示该dependency只会在test compilation and execution phases使用。例如在使用junit依赖时,scope参数的值为test来告诉Maven这个依赖项只是在测试阶段所需的,而不是运行时所需的资源。
  • system - this scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository. 后面会详细讲解
不同的scope会影响“依赖的依赖”的scope。下表列出的就是当一个“依赖”的scope设置为最左一列的scope,那么设置成最上一行scope的“依赖的依赖”的scope将发生的变化列表。
 
Compile
provided
runtime
test
compile
compile(*)
-
runtime
-
provided
Provided
provided
provided
-
runtime
Runtime
-
runtime
_
Test
Test
-
test
-
 
 
Dependency Management
在parent pom里使用< dependencyManagement>来简化dependency设置。举个例子:
Project A:
<project>
 ...
 <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
 </dependencies>
</project>
 
Project B:
<project>
 ...
 <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
 </dependencies>
</project>
 
下面通过在parent pom里使用< dependencyManagement>来管理child pom要使用的dependencies。
Parent Project:
<project>
 ...
 <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>group-a</groupId>
        <artifactId>artifact-a</artifactId>
        <version>1.0</version>
 
        <exclusions>
          <exclusion>
            <groupId>group-c</groupId>
            <artifactId>excluded-artifact</artifactId>
          </exclusion>
        </exclusions>
 
      </dependency>
 
      <dependency>
        <groupId>group-c</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
        <type>war</type>
        <scope>runtime</scope>
      </dependency>
 
      <dependency>
        <groupId>group-a</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
        <type>bar</type>
        <scope>runtime</scope>
      </dependency>
    </dependencies>
 </dependencyManagement>
</project>
 
使用上面parent pom就会简化child pom的dependency设置:
<project>
 ...
 <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
    </dependency>
 
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <!-- This is not a jar dependency, so we must specify type. -->
      <type>bar</type>
    </dependency>
 </dependencies>
</project>
 
注意:在上面的dependency引用中,非jar的必须使用<type> element
Dependency management的另一个很有用的用处就是控制dependency的版本。还是举例:
Project A:
<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>A</artifactId>
 <packaging>pom</packaging>
 <name>A</name>
 <version>1.0</version>
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.2</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>b</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>c</artifactId>
       <version>1.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>d</artifactId>
       <version>1.2</version>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>
 
Project B:
<project>
 <parent>
    <artifactId>A</artifactId>
    <groupId>maven</groupId>
    <version>1.0</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>B</artifactId>
 <packaging>pom</packaging>
 <name>B</name>
 <version>1.0</version>
 <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>test</groupId>
        <artifactId>d</artifactId>
        <version>1.0</version>
      </dependency>
    </dependencies>
 </dependencyManagement>
 <dependencies>
    <dependency>
      <groupId>maven-test</groupId>
      <artifactId>a</artifactId>
      <version>1.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>maven-test</groupId>
      <artifactId>c</artifactId>
      <scope>runtime</scope>
    </dependency>
 </dependencies>
</project>
 
上面可以看出project A是project B的parent,A和B都定义a, c, d dependencies,那么如果对project B执行maven命令,会采用哪个定义的呢?答案如下:
  • dependency a and c将会采用1.0版本。尽管在parent project A里定义的a and d的版本是1.2,但根据dependency mediation "nearest definition"特性,采用的是project B定义的版本。
  • dependency b只在parent project A里有定义,因此就采用project A的定义。即使Dependency c会使用不同版本的b, 如果执行project B还是会采用project A定义的版本(还是根据dependency mediation "nearest definition"特性)。
  • dependency d的情况和dependency b的差不多:由于它在AB都用定义,因此是采用project B定义的版本1.0。假如Dependency c会使用不同版本的d, 如果执行project B还是会采用project B定义的版本(还是根据dependency mediation "nearest definition"特性)。
System scope Dependency
System scopedependencies总是available的,而且不需要从repository里获取,因为定义成system scopedependencies都是由JDK or VM提供的。典型的例子就是JDBC standard extensionsJava Authentication and Authorization Service (JAAS).
例子:
<project>
 ...
 <dependencies>
    <dependency>
      <groupId>javax.sql</groupId>
      <artifactId>jdbc-stdext</artifactId>
      <version>2.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
 </dependencies>
 ...
</project>
如果你的artifact来自JDK's tools.jar,那么system path应该定义为:
<project>
 ...
 <dependencies>
    <dependency>
      <groupId>sun.jdk</groupId>
      <artifactId>tools</artifactId>
      <version>1.5.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/../lib/tools.jar</systemPath>
    </dependency>
 </dependencies>
 ...
</project>