《Maven实战》读书笔记

目标

主要介绍maven的基本概念和工作机制,基于Maven实战

坐标和依赖

依赖管理的基础是坐标,maven仓库也基于maven坐标管理

坐标

  • maven的坐标包括groupId、artifactId、version、packaging、classifier,前三个必须,packaging可选,classifier不能直接定义
    • groupId,定义当前项目隶属的实际项目。groupId不应该对应项目隶属的公司或者组织,格式为域名反向格式。
    • artifactId,定义实际项目中的一个Maven项目(模块)。默认情况下,Maven生成的构件,会以artifactId作为开头,因此建议artifactId以实际项目为前缀
    • version,该元素定义Maven项目当前所处的版本
    • packaging,该元素定义Maven项目的打包方式,打包方式通常与所生成构件的文件扩展名对应,其次打包会影响到构件的生命周期。默认值为jar
    • classifier,该元素用来帮助定义构件输出的一些附属构件。附属构件与主构件对应,比如同时生成包含javadoc,sources的jar包就是附属构件,这时javadoc和classifier就是这两个附属构件的classifier。classifier不能直接在项目部分定义,而是在插件部分定义。因为附属构件是插件生成的。
  • 一般项目构件的文件名为artifactId-version[-classifier].packaging
  • mvn clean install会将构件安装到本地仓库,默认就在本地的~/.m2/repository目录下

依赖

依赖声明可以包含如下内容:

<dependencies>
    <dependency>
        <groupId>...</groupId>
        <artifactId>...</artifactId>
        <version>...</version>
        <type>...</type>
        <scope>...</scope>
        <optional>...</optional>
        <exclusions>
            <exclusion>
            ...
            <exclusion>
        <exclusions>
    </dependency>
</dependencies>
  • 元素位于元素下,下面可以包含多个元素
  • 每个元素包含的内容有:
    • groupId, artifactId, version作为依赖的基本坐标,用于定位依赖
    • type,依赖的类型,对应于packaging,默认为jar
    • scope,依赖的范围
    • optional,标记依赖是否可选
    • exclusions,用来排除传递性依赖
依赖范围
  • maven在编译项目的时候使用一套classpath,在编译和执行测试的时候使用另外一套classpath,在实际运行的时候,会另外再使用一套classpath。依赖范围用来控制这三种classpath,因此有如下几种依赖范围
    • compile,编译依赖范围,默认值。使用此依赖范围的依赖对于编译、测试、运行三种classpath都有效
    • test,测试依赖范围,使用此依赖范围的依赖只对于测试classpath有效。典型的就是Junit依赖一般声明为测试依赖范围。
    • provided,已提供依赖范围,使用此依赖范围的依赖,对于编译和测试classpath有效,但在运行时无效,典型的例子是servlet-api,编译和测试的时候需要,但运行时由容器提供。
    • runtime,运行时依赖范围,使用此依赖范围的依赖,对于测试和运行classpath有效,但在编译主代码是无效。典型的例子是JDBC驱动实现,项目主代码的编译仅需要JDK的JDBC接口,只有在测试和运行时才需要实现。
    • system,系统依赖范围,该依赖与三种classpath的关系与provided依赖范围完全一致,但是使用system依赖范围的依赖必须通过systemPath元素显式的指定依赖文件的路径。
    • import,导入依赖范围,该依赖范围不会对三种classpath产生实际影响。
传递性依赖与依赖范围
* 当第二直接依赖范围为compile的时候,传递性依赖的范围与第一直接依赖范围一致
* 当第二直接依赖范围为test的时候,依赖不会传递
* 当第二直接依赖范围为provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖的范围也是provided
* 当第二直接依赖范围为runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递依赖的范围为runtime。
依赖调解

解决传递性依赖时,引入同一包的不同版本的问题。
这种情况下maven选择依赖的原则是1)路径最近这优先,2)路径层数相同的情况下,声明在前者优先。

依赖最佳实践
  • 排除依赖,使用元素,下只需要groupId和artifactId,不需要version。因为maven解析后的依赖中不会存在版本不同的两个包。
  • 归类依赖,对于相互关联的同一组依赖,比如springframework,用maven属性来管理它们的version字段,可以保证依赖版本的统一。使用元素定义属性,使用${property-name}引用属性。
  • 优化依赖
    • mvn dependency:list可以查看当前的所有依赖。
    • mvn dependency:tree可以以树形结构查看。
    • mvn dependency:analyze可以分析依赖中存在的问题。可以发现用到未显示声明的依赖,也可以发现声明但未显示用到的依赖,但是仅做静态分析,对于运行时依赖无法发现。

仓库

中央仓库,定义在超级pom中,位于 M2HOME/lib/mavenmodelbuilder version.jar中,超级pom是所有maven项目都要继承的pom。

在pom中配置远程仓库
  • 可以使用的子元素来在项目中定义远程仓库,每个元素定义一个远程仓库,每个repository的id必须唯一。
  • releases元素用来控制发布版构件的下载,true表示下载发布版本的构件。
  • snapshots元素用来控制快照版构件的下载,false表示不会下载快照版本的构件。
  • layout元素的值为default表示采用maven2和maven3的布局,而不是maven1的布局legacy。
  • releases和snapshots还有两个子元素updatePolicy和checksumPolicy
    • updatePolicy用来配置Maven从远程仓库的更新频率,默认值是daily,可用值还有never、always、interval:X(分钟)。
    • checksumPolicy用来配置Maven检查文件校验和的策略。当构件被部署到Maven仓库时会同时不是对应的校验和文件,如果校验和验证失败,且checksumPolicy为默认值warn,则输出警告信息,可用还包括fail、ignore。
<project>
    ...
    <repositories>
        <repository>
            <id>jboss</id>
            <name>JBoss reposiotry</name>
            <url>http://repository.jboss.com/maven2/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <layout>default</layout>
        </repository>
    </repositories>
    ...
</project>
远程仓库认证

认证信息必须配置在settings.xml文件中,不能像仓库一样配置在pom中。这样比较安全,因为settings是在本机的,pom是在代码库中的。
仓库信息认证配置举例,配置中的id要与仓库的id保持一致

<settings>
    <servers>
        <server>
            <id>repository id here</id>
            <username>repo-user</username>
            <password>repo-pwd</password>
        </server>
    </servers>
</settings>
部署至远程仓库

可以在pom中进行配置,将项目生成的构件部署至指定的远程仓库,这里利用的是pom中的子元素

<project>
    <distributionManagement>
        <repository>
            <id>proj-releases</id>
            <name>project release repository</name>
            <url>http://x.x.x.x/maven2/repositories/proj-releases</url>
        </repository>
        <snapshotRepository>
            <id>proj-snapshots</id>
            <name>project snapshot repository</name>
            <url>http://x.x.x.x/maven2/repositories/proj-snapshots</url>
        </snapshotRepository>
    </distributionManagement>
</project>

在pom中配置好要部署到的远程仓库后,执行maven clean deploy就可以将输出的构件部署到对应的远程仓库了。

快照版本

快照版本是为了协同开发时,需要频繁从远程仓库下载最新构件而又不希望更新版本号而发明的手段。快照版本会由maven自动为构件打上时间戳,这样就能永远获取最新的构件。快照版本只应该在组织内部的项目或者模块间依赖使用。

从仓库解析依赖的机制
  1. 当本地仓库没有依赖的构架的时候,Maven会自动从远程仓库下载,当依赖版本为快照时,maven会自动找到最新的快照。过程如下:
  2. 当依赖范围为system时,maven直接从本地文件系统解析构件
  3. 根据依赖坐标计算构件在仓库中的路径后,尝试从本地仓库寻找构件,如果有,则解析成功
  4. 如果本地没有,如果依赖的是发布版构件,则遍历所有远程仓库,发现后下载使用
  5. 如果依赖版本是RELEASE或LATEST,则基于更新策略读取所有仓库的元数据groupId/artifactId/maven-metadata.xml将其与本地仓库的对应元数据合并后,计算出RELEASE或者LATEST的真实值,然后基于真实值去下载构件。
  6. 如果依赖的版本是SNAPSHOT,则基于更新策略去读取所有远程仓库的元数据groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后根据真实值检查本地仓库或下载。
  7. 如果最后解析得到的是时间戳格式的快照,则复制其时间戳格式的文件至非时间戳格式,如SNAPSHOT,并使用该非时间戳格式的文件。
  8. 建议不要在依赖声明中使用RELEASE、LATEST等不明晰的声明。
镜像

镜像和私服不同,镜像是原始仓库的完全拷贝,能够连接远程仓库的私服则像是个带有缓存的代理。配置了镜像后会完全屏蔽对被镜像库的访问,因此镜像库的稳定性很重要。
镜像配置举例,在settings.xml中配置

<settings>
    <mirrors>
        <mirror>  
            <id>nexus-osc</id>  
            <mirrorOf>central</mirrorOf>  
            <name>Nexus osc</name>  
            <url>http://maven.oschina.net/content/groups/public/</url>  
        </mirror>
    </mirrors>
</settings>

其中元素配置被镜像库的id,可以使用通配符。匹配所有仓库,external:匹配所有远程仓库,repo1,repo2可以指定多个仓库,*,!repo1,匹配所有远程仓库,repo1除外,使用!将仓库从匹配中排除。使用通配符的mirrorOf可以配合私服使用,在私服上配置所有远程库,本地只需要配置镜像为私服即可访问多个远程库。

生命周期和插件

maven的生命周期对构件过程进行抽象,主要包含项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等构建步骤。生命周期抽象了构建的各个步骤,定义了它们的次序,但是没有提供具体的实现,具体的实现通过插件机制来完成。每个构建步骤可以绑定一个或者多个插件行为。
maven日常使用中,命令行的输入往往对应了生命周期,maven的生命周期是抽象的,生命周期本身并不做任何实际工作,实际行为都由插件来完成。maven为大部分生命周期绑定了默认插件,用户可以配置插件的行为来控制构建过程。

三套生命周期

maven有三套生命周期,分别是clean、default和site。每个生命周期都包含一些有顺序的阶段,后面的阶段依赖于前面的阶段,用户和maven最直接的交互方式就是调用这些生命周期的阶段。各套生命周期之间是项目独立的,而一个生命周期的阶段是有前后依赖关系的。
* clean生命周期,目的是项目清理,包含的阶段有pre-clean、clean和post-clean三个阶段。
* pre-clean,执行一些清理前需要完成的工作
* clean,清理上一次构建生成的文件
* post-clean,执行一些清理后需要完成的工作
* default生命周期,目的是构建项目,定义了构建时所需要执行的所有步骤,包含如下阶段
* validate,校验项目是否正确以及所有必须的信息是否齐备
* initialize
* generate-sources
* process-sources
* generate-resources
* process-resources,处理项目主资源文件,一般来说是对src/main/resources目录的内容进行变量替换等工作,然后复制到项目输出的主classpath目录中
* compile,编译项目的主源码,一般来说是编译src/main/java目录下的java文件至项目输出的主classpath目录下。
* process-classes
* generate-test-sources
* process-test-sources
* generate-test-resources
* process-test-resources
* test-compile,编译项目的测试代码
* process-test-classes
* test,使用单元测试框架进行测试,测试代码不会被打包或部署
* prepare-package
* package,接受编译好的代码,打包成可发布的格式,比如JAR、WAR
* pre-integration-test
* integration-test
* post-integration-test
* verify,运行并检查集成测试结果来保证质量
* install,将包安装到Maven本地仓库,供本地其它maven项目使用
* deploy,将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。
* site生命周期,目的是建立和发布项目站点,maven能够基于POM所包含的信息,自动生成一个项目站点,方便团队交流和发布项目信息,包括以下阶段
* pre-site,执行一些在生成项目站点之前需要完成的工作
* site,生成项目站点文档
* post-site,执行一些在生成项目站点之后需要完成的工作
* site-deploy,将生成的项目站点发布到服务器上

Maven各个生命周期的详细描述参见:官方文档
从命令行执行maven是,主要就是调用的生命周期的阶段,由于各个生命周期阶段依赖于前面的阶段,因此执行时会将该阶段前面的阶段也一并顺序执行。

插件目标和插件半丁

直观上来讲,插件目标就是mvn dependency:tree,这里的tree就是插件目标(Plugin Goal)。每个插件目标对应一个功能,冒号前面是插件前缀,冒号后面是插件目标。
maven的生命周期和插件相互绑定,具体来说是生命周期的阶段与插件目标绑定。

内置绑定

为了实现零配置使用,maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段时,对应的插件目标就会执行相应的任务。
默认生命周期阶段与插件目标的绑定关系如下表:

生命周期阶段插件目标
pre-clean
cleanmaven-clean-plugin:clean
post-clean
pre-site
sitemaven-site-plugin: site
post-site
site-deploymaven-site-plugin:deploy
process-resourcesmaven-resources-plugin:resources
compilemaven-compiler-plugin:compile
process-test-resourcesmaven-resources-plugin:testResources
test-compilemaven-compiler-plugin:testCompile
testmaven-surefire-plugin:test
packagemaven-jar-plugin:jar
installmaven-install-plugin:install
deploymaven-deploy-plugin:deploy
自定义绑定

可以在插件中配置phase将插件目标绑定到某个特定的生命周期阶段。
代码示例如下,如下将maven-source-plugin:jar-no-fork目标绑定到default生命周期的verify阶段

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins<groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

一般插件编写时也会定义默认绑定阶段,可以通过mvn-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:3.3 -Ddetail
当多个插件目标绑定到同一个阶段时,这些插件声明的先后顺序决定了目标的执行顺序。

插件配置

除了配置插件目标绑定外,还可以对目标的参数进行一些配置。用户可以通过命令行和POM来配置这些参数。
可以在插件配置中定义插件任务,是插件配置仅应用到任务,而不是全局。

获取插件信息

基本上所有的插件都来自Apache和Codehaus
插件信息可以访问插件站点来获取,也可以使用maven-help-plugin插件来获取帮助。

插件解析机制

为了方便用户使用和配置插件,maven不需要用户提供插件的完整坐标就可以解析得到正确的插件,使用插件前缀可以简单的引用插件。
与普通构件一样插件构件也基于坐标存储在maven仓库中。插件的远程仓库配置与普通构件的远程仓库配置不同,插件的远程仓库使用元素来定义,在配置插件时,如果是官方插件可以省略groupId定义,不推荐这样使用。当没有配置版本时,会拉取所有远程插件仓库和本地仓库的元数据库中获取最新版本,这里maven 3不会获取快照版本。
用户可以使用插件前缀来简洁的访问插件,这里插件前缀与groupId:artifactId一一对应,对应关系存储在仓库元数据库中,位于groupId/maven-metadata.xml中。这里的groupId默认指的是org.apache.maven.plugins和org.codehaus.mojo这两个,也可以通过配置settings.xml,通过元素配置自定义的插件扫描的groupId。
在上述的maven-metadata.xml中存在元素描述插件的前缀。

聚合和继承

聚合

可以用一个项目聚合其它项目,实现一起编译打包。聚合项目的packaging值为pom,使用元素来包含各个子模块。这里每个module的值都是一个当前pom的相对目录。一般为了快速定位内容,模块所处的目标名称应该与其artifactId一致。为了方便用户构建项目,一般将聚合模块放在项目目录的最顶层,其它模块作为聚合目录的子目录存在。

继承

pom可以继承,作为父模块的pom,其packaging类型必须为pom。父模块的目的是为了消除重复的配置,它本身不包含除POM之外的项目文件,也不需要src/main/java之类的文件夹。可以在子模块中使用元素来声明父模块,子元素声明父pom的位置,默认是../pom.xml。

可以继承的pom元素包括:
* groupId和version
* description,项目描述信息
* organizition
* inceptionYear, 项目的创始年份
* url,项目的URL地址
* developers,项目的开发者信息
* contributors,项目的贡献者信息
* distributionManagement,项目的部署配置
* issueManagement,项目的缺陷跟踪系统信息
* ciManagement,项目的持续集成系统信息
* scm,项目的版本控制系统信息
* mailingLists,项目的邮件列表信息
* properties,自定义的maven属性
* dependencies,项目的依赖
* dependencyManagement,项目的依赖管理配置
* repositories,项目的仓库配置
* build,包括项目的源码配置,输出目录配置,插件配置,插件管理配置等
* reporting,包括项目的报告输出配置,报告插件配置等。

依赖继承管理

maven提供的dependencyManagement元素能够让子模块集成到父模块的依赖配置,还能保证子模块依赖使用的灵活性。在dependencyManagement下声明的依赖不会引入实际的依赖,但它能够约束dependencies下的依赖使用。可以简化子模块dependencies的编写,在父模块中通过元素声明过的依赖,在子模块中只需要指明groupId和artifactId即可,其它配置可以从父模块继承。还可以配置import依赖范围使用,指的就是将指定pom的配置导入。import依赖范围一般指向packaging值为pom的模块。
除了元素用来管理普通依赖外,maven还提供了元素来管理插件。该元素配置的插件依赖不会导致实际的插件调用行为,只有pom中配置了真正的元素并且groupId和artifactId与中声明的一致时,的配置才会影响插件的行为。

约定优于配置

原因:使用约定可以大量减少配置,大量的约定配置从超级pom来继承,比如源码的位置,输出目录,默认打包方式等。

超级pom

maven 3的超级pom位置是$M2_HOME/lib/maven-model-builder-x.x.x.jar中的org.apache.maven.model.pomx-4.0.0.xml。
超级pom的内容包括:
* 中央仓库的定义
* 中央插件仓库的定义
* 项目结构的定义,由元素定义,包括
* 项目的主输出目录,由子元素定义
* 项目主代码输出目录,由子元素定义
* 最终构件的名称格式,由子元素定义
* 测试代码输出目录,由子元素定义
* 主源码目录,由子元素定义
* 脚本源码目录,由子元素定义
* 测试源码目录,由子元素定义
* 主资源目录,由子元素的子元素的子元素定义
* 测试资源目录,由子元素的子元素的子元素定义
* 插件版本定义,由pluginManagement元素定义
* 项目报告输出目录的配置,项目发布profile等。

反应堆

就是多模块聚合在一起的构建项目,需要关注的是各个模块的构建顺序。
构建顺序的计算规则如下,maven按照module声明的顺序读取pom,如果该pom没有依赖模块,那么就构建该模块,否则先构建其依赖模块,如果该依赖模块还依赖于其它模块,则进一步构建其它依赖模块。模块间的依赖关系构成一个有向非循环图,依赖之间不允许出现循环,否则会报错。

使用Maven进行测试

使用maven构件WEB应用

版本管理

灵活构件,maven技巧,项目站点,Archetype

常用插件列表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值