下面pom.xml文件是运行mvn archetype:generate这个目标自动生成的HelloWorld入门maven项目的项目对象模型文件,如下:
<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.0modelVersion>
<groupId>com.mycompany.helloworldgroupId>
<artifactId>helloworldartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<name>helloworldname>
<url>http://maven.apache.orgurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
dependencies>
project>
1,依赖jar加载过程
首先要明确,在pom.xml中定义了很多依赖。在项目启动后的不同的阶段,maven首先会去本地仓库根据maven坐标寻找这些jar并根据依赖的scope来决定不同的阶段加载这些依赖jar进类路径。如果本地仓库没有寻找到,maven会根据坐标去maven中央仓库寻找并联网下载这些jar进入本地类路径。
2,坐标
不论是本地仓库,还是中央仓库,都会存在很多的构件,比如jar和war等等的。在如此众多的构件中,就是通过groupId,artifactId,version,packaging和classifier来唯一标识一个构件,也就是maven坐标。先来了解一下各个坐标元素的情况:
现在已经很清楚maven坐标是怎么回事了吧,要特别注意项目和依赖等所有构件在maven中都是由这几个坐标元素所唯一标识。
1,依赖范围scope
先来看看maven项目构建的过程是怎样的,这里引用了别的博客的图:
由上图可知,maven构建项目的主要过程是编译,测试和部署运行。而每一个过程是需要依赖一些jar才能完成的,这个大家都知道,java源代码不可能裸奔,必须有一些基础的类库才行。但是java是怎么能够找到这些类库或者说依赖的jar来支持每一步的运行呢?浅显的理解就是,JVM会首先去检查系统的环境变量,去类路径classpath下去找这些jar。所以我们必需把项目不同运行时期需要的jar等构件放入类路径。
特别要注意,并不是所有的构件一旦加载进类路径就一直存在于类路径的,因为maven不同的运行时期时加载进类路径的一些构件,这些只是暂存其中的,也把暂存一些构件的类路径称为临时类路径。每个运行时期一旦结束这个临时类路径里面的构件就会被清除。这个对于maven来说,则为依赖范围scope,这个依赖范围是可以被配置的,也就决定了构件在类路径存在的时间范围。
maven项目的主要过程:编译,测试和运行。根据上面分析可知,不同的过程classpath中的构件情况是不一样的。而maven依赖范围则主要讲maven主要三个过程中系统类路径classpath中依赖有无的情况。
maven依赖范围:
1,何为依赖传递?
不知道你是否仔细看过本地仓库的一些依赖情况。本地仓库中的每一个依赖的同一个目录下面还有一个相关的pom.xml,这个pom.xml配置的则为这个依赖所需要依赖的一些构件。当你的项目中需要引入这个依赖时,这个依赖会被加载进类路径。并且,这个依赖相关的pom.xml配置的依赖也会被加载进类路径。这就形成了依赖的传递性,也就是依赖的依赖也被加载进类路径。特别要注意,在你的项目中,有些依赖的依赖会被用到,但是有些依赖的依赖并不会被用到。所以我们需要把这些不需要的依赖的依赖去除,也就是不让它被加载进本地仓库,更不让它加载进类路径。
2,依赖范围机制对依赖传递的影响
maven的依赖范围机制不仅决定了依赖在编译测试运行三个过程在classpath存在情况,还会对依赖之间的传递性产生影响。
这个依赖之间的情况如上图,是由第一直接依赖的依赖范围和第二直接依赖的依赖范围决定着传递依赖的依赖范围,也就是决定着你的项目在三个过程在类路径是否存在依赖的依赖。情况如下,该图截自《Maven实战》:
3,传递性依赖两个解决原则
特别注意,由于这两个原则,在项目的类路径classpath下,只可能存在两个版本依赖的其中一个。
4,依赖可选性optional
书上这样的一个专业术语,差点没搞懂,我宁愿反过来来理解。在pom.xml中对某个依赖的optional设置为true,则这个依赖对于这个pom.xml所属的项目为可选性依赖。举个例子,比如我们的持久层可以采用多种数据库Mysql和Oracle等来实现,我们可以把这两个数据库的JDBC驱动都在pom.xml配置并且把optional设置为true,那么这两种驱动jar对于本项目来说则为可选性的依赖,选择哪个都可以,但是一次只能选择一个。特别要注意,此时两个驱动依赖都会被加载进类路径,也就是可选依赖只会当前项目产生影响。如下:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.10version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>postgresqlgroupId>
<artifactId>postgresqlartifactId>
<version>8.4-701.jdbc3version>
<optional>trueoptional>
dependency>
如上图,A的第一直接依赖为B,C和D为B的可选性依赖。特别注意,由于可选依赖只对当前项目产生影响,则在A项目或者依赖A所在的项目运行时,并不会进入类路径。如果在A项目或者依赖A所在的项目中需要C或者D,则还需要在其pom.xml进行配置。
5,maven依赖的最佳实践
<dependency>
<groupId>agroupId>
<artifactId>a-m1artifactId>
<version>2.4version>
<exclusions>
<exclusion>
<groupId>bgroupId>
<artifactId>b-m1artifactId>
exclusion>
exclusions>
dependency>
1,如果传递性依赖为SNAPSHOT不稳定版本,则在项目中直接声明该依赖的稳定性版本。由上文路径最短者优先原则可知,因为直接声明了该依赖的稳定版本,所以maven会把路径更长的传递性依赖去除。2,如果第一直接依赖为不稳定版本,直接换成稳定版本。
把版本相同的很多依赖归类。例如spring项目有很多的项目模块,每个版本的项目spring,它的所有的项目模块的版本也就一样。可以在pom.xml利用全局属性元素properties先定义好spring项目版本号,然后下面的很多spring模块再引用即可。实例如下:
<properties>
<springframework.version>2.5.6springframework.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>{springframework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>{springframework.version}version>
dependency>
<dependencies>
个人声明:本博文是在学习中,根据自己的理解所写。由于个人理解和专业素养有限,望指教,多谢!
参考资料:
1,《Maven实战》
2,《Apache Maven 入门篇》(系列教程)
3,《官方文档》
4,《maven可选依赖和依赖排除》