基本概念
是什么?
Maven是一个强大的Java项目构建工具。
什么是构建工具?
构建工具是将软件项目构建相关的过程自动化的工具。构建一个软件项目通常包含以下一个或多个过程:
- 生成源码(如果项目使用自动生成源码);
- 从源码生成项目文档;
- 编译源码;
- 运行单元测试
- 将编译后的代码打包成JAR文件或者ZIP文件;
- 将打包好的代码安装到服务器、仓库或者其它的地方;
- 叙述项目间的依赖关系
有些项目可能需要更多的过程才能完成构建,这些过程一般也可以整合到构建工具中,因此它们也可以实现自动化。
自动化构建过程的好处是将手动构建过程中犯错的风险降到最低。而且,自动构建工具通常要比手动执行同样的构建过程要快。
核心概念:POM文件(POM project object mode项目对象模型)
POM文件是以XML文件的形式表述项目的资源,如源码、测试代码、依赖(用到的外部Jar包)等。
pom主要组成部分:
简单运行
当你安装好了Maven,并且在项目的根目录下创建了POM文件,可以在项目上运行Maven了。
mvn clean install
该命令首先执行clean构建周期,删除Maven输出目录中已编译的类文件,然后执行install构建阶段。
maven深入实践
构建生命周期
Maven中内置了三个构建生命周期:default,clean和site。default生命周期处理工程的部署,clean生命周期处理工程的清理,而site生命周期则负责创建工程的站点文档。
packaging直接影响Maven的构建生命周期。
在Maven 3中,其可用的打包类型如下:
- jar,默认类型
- war
- ejb
- ear
- rar
- par
- pom
- maven-plugin
如果想一次打包多个项目,可采用聚合类型pom,它通常由一个父模块和若干个子模块构成。
<packaging>pom</packaging>
<modules>
<module>zy-service-consumer</module>
<module>zy-eureka-server</module>
<module>zy-zipkin-service</module>
<module>zy-hystrix-dashboard</module>
</modules>
构建生命周期是由阶段组成的
三个构建生命周期都是由一系列不同的构建阶段组成,每一个构建阶段代表了生命周期的一个阶段。
以default生命周期为例,它是由以下的阶段组成
- validate – 验证该项目是否正确,所有必要的信息都是可用的
- compile – 编译工程源码
- test – 使用一个合适的单元测试框架测试编译的源代码。这些测试的代码不会被打包或部署到项目中
- package – 将编译的代码打包成它发布的格式,例如JAR
- integration-test – 如果必要的话,该命令会将工程处理并部署在一个集成测试运行的环境中
- verify – 运行任何检查以验证该包是否有效,是否符合质量标准
- install – 将工程打包安装到本地仓库中,以便本地其他项目可以进行依赖
- deploy – 在集成或发布环境中,将最终工程打包复制到远程仓库中,用于与其他开发人员和项目共享
mvn deploy
它不仅执行指定的构建阶段,而且会执行指定构建阶段之前的每一个阶段。因此,执行
构建阶段使用的构建插件
mvn install
[INFO] ------------------------------------------------------------------------
[INFO] Building zy-zeromq 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ zy-zeromq ---
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ zy-zeromq ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ zy-zeromq ---
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ zy-zeromq ---
[INFO]
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ zy-zeromq ---
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ zy-zeromq ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ zy-zeromq ---
(查看完整的生命周期阶段列表,请参考生命周期参考)
仓库
仓库只分为两类:本地仓库和远程仓库。
当Maven根据坐标寻找构建的时候,首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会到远程仓库查找,发现需要的构件后,将其下载到本地仓库再使用。如果本地仓库和远程仓库都不存在需要的构件,则报错。
私服
私服的好处:节约外网的带宽,加速Maven的构建,方便部署第三方构件,稳定等
部署到远程仓库
将本项目jar包上传到私服、远程仓库,供项目组或其他人使用。配置如下:
pom.xml
<distributionManagement>
<repository>
<id>thirdparty</id>
<name>3rd party</name>
<url>http://192.168.100.103:8081/nexus/content/repositories/thirdparty/</url>
</repository>
</distributionManagement>
settigns.xml配置认证
<servers>
<server>
<id>thirdparty</id>
<username>repo-user</username>
<password>repo-psw</password>
</server>
</servers>
mvn命令:
本地仓库 的更新
mvn clean package install
远程 仓库 的更新
mvn clean package deploy
如果编译出问题时,可以执行如下命令
mvn clean install -e -U
-e详细异常,-U强制更新
为了加速下载,可以配置一些镜像mirror。
依赖
依赖传递性
- 子项目可以继承父项目的依赖
- 项目A依赖项目B,项目A-->项目B,项目B中的依赖也会被加入到项目A中。
由于依赖的传递性,导致项目中存在多个同名的jar包,存在jar包冲突等。maven提供的以下机制来解决这些问题:
依赖调解原则
过从Maven 2.0.9开始,POM中依赖声明的顺序决定了哪个版本会被使用,也叫作”第一声明原则”。
依赖管理
在多模块应用中,可能多个子项目会有共同的依赖。
在顶层的pom中指定依赖项的版本,以便让所有的子项目使用同一版本。dependency management只是依赖的一个声明,子项目需显式引用,但不用指定版本号。
父项目pom
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子项目pom,需显式引用依赖,但不用指定版本号。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
依赖范围
- compile 默认的scope,表示 dependency 都可以在生命周期中使用。而且,这些dependencies 会传递到依赖的项目中。适用于所有阶段,会随着项目一起发布
- provided 跟compile相似,但是表明了dependency 由JDK或者容器提供,例如Servlet AP和一些Java EE APIs。这个scope 只能作用在编译和测试时,同时没有传递性。
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
- runtime 表示dependency不作用在编译时,但会作用在运行和测试时,如JDBC驱动,适用运行和测试阶段。
- system 跟provided 相似,但是在系统中要以外部JAR包的形式提供,maven不会在repository查找它。
- import(Maven2.0.9及以上)
import范围只适用于pom文件中的部分。表明指定的POM必须使用 部分的依赖。因为依赖已经被替换,所以使用import范围的依赖并不影响依赖传递。 - test 范围表明使用此依赖范围的依赖,只在编译测试代码和运行测试的时候需要,应用的正常运行不需要此类依赖。
依赖排除
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
- 小技巧:一般在编译时类找不到,但是实际存在jar冲突情况时。在idea中安装Maven Helper插件,根据jar名称搜索,定位到具体的依赖,在依赖中排除冲突jar包。
可选依赖
可选的依赖工作原理
Project-A -> Project-B
上面的图意味着项目A依赖于项目B,当A在它的POM文件中把B声明为一个可选的依赖,他们的关系依然没有改变。仅仅就像一次正常的构建,在这次构建中,项目B将会被添加进classpath。
Project-X -> Project-A
但是当一个其他的项目(项目X)在它的POM文件中声明项目A为一个依赖,这个可选的依赖就发挥作用了。你将会注意到项目X的classpath不会包含项目B:为了把B包含进项目X的classpath,你需要在你的POM文件中直接声明。
例子:
有一个名为X2的项目,这个项目和hibernate有一些类似的功能,支持许多数据库驱动/依赖,比如说mysql,postgre,oracle等。为了构建X2,所有的这些依赖都是必须的,但是对于你的项目来说却不是必须的,所以对于X2把这些依赖声明为可选的是非常实用的,不论什么时候当你在POM文件中把X2声明为一个直接依赖的时候,所有被X2支持的驱动不会自动的被包含进你的项目的classpath,你需要直接声明你将要使用的数据库的依赖/驱动。
自定义打包maven-assembly-plugin
在运行mapreduce/storm时,一般是以jar包方式运行,每个jar包中依赖的jar的包,可以采用maven-assembly-plugin。
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}-${project.version}</finalName>
<attach>false</attach>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase><!-- 绑定到package生命周期阶段上 -->
<goals>
<goal>single</goal><!-- 只运行一次 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
assembly.xml
<!--是否生成和项目名相同的根目录-->
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/target/classes</directory>
<outputDirectory>.</outputDirectory>
</fileSet>
<!--<fileSet>
<directory>${project.basedir}/src/main/resources</directory>
<outputDirectory>./</outputDirectory>
</fileSet>-->
</fileSets>
<dependencySets>
<dependencySet>
<!--是否把本项目添加到依赖文件夹下-->
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>/lib</outputDirectory>
<!--<excludes>
<exclude>org.apache.storm:storm-core</exclude>
</excludes>-->
</dependencySet>
</dependencySets>
其他常见配置方法
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
...
</configuration>
这种方式打出的包是fat jar,也就是把依赖的jar包全部解压成class文件后,再与自己的代码打成一个jar包。
maven assembly
命令
- mvn clean 清除目标目录中的生成结果
- mvn package 依据项目生成 jar 文件,打包之前会进行编译,测试
- mvn install在本地 Repository 中安装 jar
跳过测试类 : -Dmaven.test.skip=true
下载jar包源码: -DdownloadSource=true
下载javadocs: -DdownloadJavadocs=true
Archrtype
原型(Archetype)是一个Maven工程模板工具包。
maven-archetype-quickstart用得比较多。
maven vs gradle
- maven和gradle的比较与使用
总的来说,gradle相比于maven有很打的灵活性,目前gradle的使用者越来越多。而由于maven之前处于主流,并且在某些方面maven较gradle还有一些优点,所以maven和gradle并存。在github上大多数优秀的开源项目会同时提供maven和gradle两种添加依赖的方式。
参考文献
tips:本文属于自己学习和实践过程的记录,很多图和文字都粘贴自网上文章,没有注明引用请包涵!如有任何问题请留言或邮件通知,我会及时回复。