参考:https://juvenshun.iteye.com/blog/213959
https://www.cnblogs.com/build-up/p/4973358.html
一、前言
假期里看完了《maven实战》。果然会用了看理解起来也快很多。
记录一下方便后期再看。
二、maven安装与说明
构建:包含编译,运行单元测试,生成文档,打包,部署等。
1.maven的安装目录
bin: 包含了mvn运行的脚本,这些脚本用来配置java命令,准备好classpath和相关的java系统属性,然后执行java命令。
boot:该目录下有个plexus-classworlds的jar包,是一个类加载器,相当于默认的java类加载器。
2.设置http代理
有时候基于安全因素考虑,需要使用通过安全认证的代理访问因特网。这种情况下,就需要为maven配置http代理,才能让它正常访问外部仓库,以下载所需要的资源。
首先确认自己无法直接访问公共的maven中央仓库,直接ping repo1.maven.org。如果不同,再检查一下代理服务器是否畅通。(telnet)然后在~/.m2/settings.xml 中修改即可,如下:(默认第一个生效)
$M2_HOME/conf/setting.xml 与 ~/.m2/setting.xml的区别:
$M2_HOME/conf/setting.xml 是全局的,整台机器上的所有用户都会收到该配置的影响;
~/.m2/setting.xml 是用户范围的,只有当前用户才会收到该配置的影响。
推荐使用~/.m2/setting.xml,主要是为了避免无意识的影响系统中的其他用户。同时,配置用户级别的setting.xml便于maven升级。因为直接修改conf目录下的setting.xml会导致maven升级不便,每次升级到新版本需要复制setting.xml。
无论是哪种IDE,当集成maven时都会安装上一个内嵌的maven,这个maven通常会比较新,但不一定很稳定。所以推荐配置本地安装的maven。
maven的核心就是pom.xml。
三、坐标详解
groupId(组), artifactId(唯一),version 这三个元素定义了一个项目基本的坐标。在maven的世界里,任何的jar, war 或者 pom 都是 基于这些基本的坐标来区分的。
当版本为SNAPSHOT时意为该项目还在开发中, 是不稳定的版本。
scope 为依赖范围,若依赖范围是test 则表示该代码只对测试有效。如果不声明依赖范围,默认就是compile,表示依赖对主代码和测试代码都有效。
groupId: 定义当前maven项目隶属的实际项目
artifactId:推荐使用实际项目名称作为artifactId的前缀。eg:nexus-indexer. 使用实际项目名称作为前缀后,就能方便从一个lib文件夹中找到某个项目的一组构件。
version
maven的快照版本发布到私服中,maven会自动为构件打上时间戳。这样,maven就能随时找到仓库中该构件最新的文件。
默认情况下,maven每天检查一次更新(由仓库的updatePolicy控制)
当项目经过完善的测试后需要发布的时候,就应该将快照版本更改为发布版本。
快照版本对应了大量的不同时间戳的构件,这也决定了其不稳定性。
项目不应该依赖于任何组织外部的快照版本依赖,由于快照版本的不稳定性,这样的依赖会造成潜在的危险。也就是说,即使项目构建今天是成功的,由于外部的快照版本依赖实际对应的构件随时可能变化,项目的构建就可能由于这些外部的不受控制的因素而失败。
packaging(可选):定义maven项目的打包方式。有三种 jar(内部调用或者作服务使用) war(需要部署的项目) pom
maven默认的打包类型是jar。默认打包生成的jar包是不能够直接运行的,因为带有main方法的类信息不会添加到manifest中(打开jar文件中的META-INF/MANIFEST.MF 文件,将无法看到Main-Class 一行)。可以借助maven-shade-plugin. 打包方式会影响到构件的生命周期,比如jar打包和war打包会使用不同的命令。
项目构件的文件名是与坐标相对应的。一般的规则是artifactId-version.packaging。注意:packaging不一定与构建扩展名相对应,比如packaging为maven-plugin的构件扩展名为jar。
每个dependency 依赖包含的元素有:
groupId artifactId version 基本坐标
type: 依赖的类型。对应于项目的packaging。该元素一般不用声明,默认值为jar.
scope
optional: 标记依赖是否可选
exclusions: 用来排除传递性依赖。exclusions可以包含多个exclusion子元素。需注意:声明exclusion时只需要groupId,artifactId,而不需要version。因为groupId,artifactId就能唯一定位依赖中的某个依赖。
归类依赖
使用常量使代码更简洁,可以避免重复。当需要改版本时只修改一处即可。
依赖范围
maven 在编译项目主代码的时候需要使用一套classpath, 在编译和执行测试的时候会使用另外一套classpath。实际运行时会使用另外一套classpath。
依赖范围就是用来控制依赖与这三种classpath(编译classpath,测试classpath,运行classpath)的关系。
compile: 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行时都需要使用该依赖。
test: 测试依赖范围。使用此依赖范围的maven依赖,只对于测试classpath有效。在编译主代码或者运行项目的使用的使用时将无法使用此类依赖。典型的例子是junit,他只有在编译测试代码及运行测试的时候才需要。
provided:已提供依赖范围。使用此依赖范围的maven依赖,对于编译和测试classpath有效,但是对运行classpath无效。(例如 servlet-api,一般是容器已经提供所以不需要重新引入)
runtime: 测试依赖范围。使用此依赖范围的maven依赖,只对于测试classpath有效。在编译主代码或者运行项目的使用的使用时将无法使用此类依赖。典型的例子是junit,他只有在编译测试代码及运行测试的时候才需要。
system:系统依赖范围。该依赖与三种classpath的关系通provided完全一致。只是使用时必须用systempath元素显式的指定依赖文件的路径。使用时需谨慎!
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/webapp/WEB-INF/lib/test-common.jar</systemPath>
</dependency>
import:该依赖范围不会对三种classpath产生实际的影响。
传递性依赖就是:A依赖B,B依赖C, A对B是第一直接依赖,B对C是第二直接依赖,A对于C是传递性依赖。
第一 第二 | compile | test | provided | runtime |
compile | compile | runtime | ||
test | test | test | ||
provided | provided | provided | provided | |
runtime | runtime | runtime
|
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。
当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
当第二直接依赖的范围是test时,依赖不会得以传递;
当第二直接依赖的范围是provided,只传递第一直接依赖的范围也为provided的依赖,且传递性依赖的范围同样是provided;
当第二直接依赖的范围是runtime, 传递性依赖的范围与第一直接依赖的范围一致;但compile例外,此时依赖的范围是runtime.
当依赖重复时,maven依赖调节的第一原则是:路径最近者优先。路径相同时,遵循第二原则:第一声明者优先,在pom中谁先声明取哪个。
maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围。对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本再依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
显示声明任何项目直接用到的依赖。
mvn dependency: list 查看已解析依赖
mvn dependency: tree 依赖树(传递依赖)
mvn dependency: analyee 只分析编译主代码和测试代码需要用到的依赖。
1.项目中使用导弹没有显示声明的依赖。一般是通过直接依赖传递来的,有可能导致当前项目出错。
2.项目中未使用但是显式声明的依赖。这种依赖不能直接删除可能执行测试或者运行时才用到。
项目的主代码会被打包到最终的构建中,而测试代码只在运行测试时使用,不会被打包。
maven的核心插件之一:compiler插件默认只支持编译java1.3,因此需要配置该插件支持java7.
surefire插件maven负责测试的插件,运行测试用例,并且输出测试报告,显示一共运行了多少测试,失败了多少,出错了多少,跳过了多少。
仓库:maven可以在某个位置统一存储所有的maven项目共享的构件,这个统一的位置就是仓库。
任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径。该路径与坐标的大致对应关系是:
groupId/artifacId/version/artifactId-version.packaging
三、仓库
仓库只分两类:本地仓库和远程仓库。当maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件或者需要查看是否有更新的版本,maven就会去远程仓库查找,发现构件后下载到本地仓库再使用。
本地仓库:
自定义本地仓库目录地址:
在~/.m2/setting.xml ,设置localRepository 元素的值为自定义仓库路径。
需要注意的是,默认情况下,~/.m2/setting.xml是不存在的,需要从maven的安装目录下/conf 下复制一份。
安装好maven后,如果不执行任何maven命令,本地仓库目录是不存在的。当用户输入第一条maven命令后,maven才会创建本地仓库,然后根据配置和需要,从远程仓库下载构件至本地仓库。
远程仓库:
对于maven来说,每个用户只有一个本地仓库,但可以配置访问很多远程仓库。
私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能不部署到私服上供其他项目使用。
中央仓库:
中央仓库是一个默认的远程仓库,maven的安装文件自带了中央仓库的位置。
然后这个文件里配置的仓库是suoyoumaven项目都会继承的超级pom。
私服:
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的maven用户使用。(盗个图)
即使在一台直接连入internet的个人机器上使用maven,也应该在本地建立私服。因为私服可以
1.节省带宽
2.加速mavne构建。
3.部署第三方构件。
4.提高稳定性,增强控制。降低中央仓库的负荷。
用私服部署至远程仓库:
<distributionManagement>
<repository>
<id>releases</id>
<name>Releases</name>
<url>http://127.0.0.1:8080/nexus/releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>Releases</name>
<url>http://27.0.0.1:8080/nexus/snapshots/</url>
<uniqueVersion>false</uniqueVersion>
</snapshotRepository>
</distributionManagement>
配置正确后,执行mvn clean deploy, maven 就会将项目构建输出的构件部署到配置对应的远程仓库中。
从仓库解析依赖的机制
1.当依赖的范围是system时,maven直接从本地文件系统解析构件。
2.如果依赖的版本是release或者latest,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出release或者latest的真实值,然后基于这个真实的值检查本地和远程仓库。
注意:在依赖声明中使用release和latest不推荐的做法。
私服相当于所有远程仓库的镜像。
四、maven的生命周期和插件
maven的生命周期是抽象的,其实际行为都是由插件来完成。
maven的生命周期包括项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有构建步骤。
每个构建步骤都可以绑定一个或多个插件行为,而且maven为大多数构建编写并绑定了默认插件。例如针对编译的maven-compiler-plugin,针对测试的maven-surefire-plugin.
maven本身不是一个单元测试的框架,他只是在构建执行到特定生命周期阶段的时候,通过插件来执行junit或者testng的测试用例,这个插件就是 maven-surefire-plugin,也可以称为测试运行器。在默认情况下,maven-surefire-plugin 的test目标会自动执行测试源码路径(默认src/test/java)下所有符合一组命名模式的测试类。
所以maven-install 并不会将测试类打包到jar包,但是在过程中maven-surefire-plugin会运行测试类。
maven拥有三套相互独立的生命周期,他们分别是clean,default和site。clean生命周期的目的是清理项目。default生命周期的目的是构建项目,而site生命周期的目的是建立项目站点。
每个生命周期包含一些阶段,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段。运行任何一个阶段的时候,它前面的所有阶段都会被运行,这也就是为什么我们运行mvn install 的时候,代码会被编译,测试,打包。
1、clean生命周期:清理项目,包含三个phase。
1)pre-clean:执行清理前需要完成的工作
2)clean:清理上一次构建生成的文件
3)post-clean:执行清理后需要完成的工作
default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分。
2、default生命周期:构建项目,重要的阶段如下。
1)validate:验证工程是否正确,所有需要的资源是否可用。
2)compile:编译项目的源代码。
3)test:使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和布署。
4)Package:把已编译的代码打包成可发布的格式,比如jar。
5)integration-test:如有需要,将包处理和发布到一个能够进行集成测试的环境。
6)verify:运行所有检查,验证包是否有效且达到质量标准。
7)install:把包安装到maven本地仓库,可以被其他工程作为依赖来使用。
8)deploy:在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。
3、site生命周期:建立和发布项目站点,阶段如下
1)pre-site:生成项目站点之前需要完成的工作
2)site:生成项目站点文档
3)post-site:生成项目站点之后需要完成的工作
4)site-deploy:将项目站点发布到服务器
例如:
mvn clean:pre-clean clean。
mvn test:default生命周期validate——>test。
mvn clean install:pre-clean clean default生命周期到validate——>install阶段
注意:
1.
maven的生命周期的阶段与插件相互绑定,用以完成实际的构建任务。对于插件本身,一个插件往往能够完成多个任务。
例如:maven-clean-plugin对应生命周期:pre-clean clean post-clean
default生命周期的阶段与插件目标的绑定关系由项目打包类型所决定。eg:jar
注意,上表只列出了拥有插件绑定关系的阶段,default生命周期还有很多其他阶段,默认它们没有绑定任何插件,因此也没有任何实际行为。
命令行插件配置:(只适合某些插件)
mvn install -Dmaven.test,skip = true // 跳过测试
pom中插件配置
聚合和继承
一般来说,一个项目的子模块都应该使用同样的groupId,如果他们一起开发和发布,还应该使用相同的version。此外,artifactId 应该使用相同的后缀,以方便同其他项目区分。
例如: groupId :aaa.bbb.ccc
artifactId: aaa-bbb-ccc-service aaa-bbb-ccc-model
${project.build.testOutputDirectory} 这是一个maven属性,默认地址为项目根目录下的target/test-classes文件夹。
对于聚合模块来说,打包方式必须为pom,否则无法构建。
用户可以通过在一个打包方式为pom的maven项目中声明任意数量的module元素来实现模块的聚合。这里每个module的值都是一个当前pom的相对目录。
一般来说,为了方便快速定位内容,模块所处的目录名称应当与其artfactId一致。
通常将聚合模块放在项目目录的最顶层。
聚合模块与其他模块的目录结构并非一定是父子关系,也可能是平行结构。当为平行结构是,目录应为../模块名。
<modules> <module>hhh-test-api</module> <module>hhh-test-model</module> <module>hhh-test-dao</module> <module>hhh-test-service</module> </modules>
在Maven中,子项目是可以继承父项目中的依赖的。同时,依赖是可以传递的,就是说假设存在三个项目,分别是项目A,项目B以及项目C,假设C依赖于B,B依赖于A,那么我们可以根据Maven项目依赖的特征不难推出项目C也依赖于A。
如果一个模块作为父母快,其打包方式也必须为pom。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent>
parent下的子元素groupId,artifactId,version这三个元素是必须的。
maven默认父pom在上一层目录。
这里详见: https://www.cnblogs.com/maxiaofang/p/5944362.html